Плагин своими руками

Функциональность программ зачастую могут улучшать не только их непосредственные разработчики, но и другие люди. Они, как правило, являются пользователями тех программ, которые хотят улучшить. Возможно это в тех случаях, когда программа поддерживает различные дополнения. Дополнения эти обычно реализуются в виде плагинов.


Что такое плагины?

Конечно, возможны всякие вариации, но обычно под плагином понимается какая-то динамически компонуемая библиотека (DLL - Dynamic Link Library) специального формата, которая благодаря находящимся в ней функциям расширяет возможности "родительского" приложения. Конечно, в широком смысле слова под плагинами можно понимать не только DLL'ки, а, например, и такие комплексные вещи, как дополнения к Mozilla Firefox. Но обычно плагин - это именно специальная динамическая библиотека.

Чем же эта динамическая библиотека такая вся из себя специальная? Всё дело в том, какие функции она экспортирует. Чтобы приложение могло загрузить эту библиотеку и использовать содержащиеся в ней функции, ему должен быть известен их вид - в общем-то, это всегда так с динамической компоновкой. Поэтому все плагины для одной программы имеют одинаковые имена и форматы функций, используемых в приложении. Конечно, среди экспортируемых функций могут быть и другие, но тогда спрашивается, зачем они там нужны.

Универсального формата плагинов, который подходил бы всем приложениям, не существует. Причина этого проста: очень разные функции выполняют разные программы, и было бы странно пользоваться плагинами к Adobe Photoshop из Sound Forge. Но, тем не менее, свои стандарты есть, а потому приложения, выполняющие сходные функции, часто "понимают" плагины своих конкурентов. Например, среди графических приложений стандартом де-факто стали уже упоминавшиеся плагины к Photoshop, а среди приложений для работы со звуком распространён формат VST.


Какие плагины писать?

Плагин зачастую включает в себя такое множество сложных вещей, что его можно считать самостоятельным программным продуктом. В первую очередь, это относится к профессиональным плагинам для сложных программ - например, к тому же 3D Studio MAX. Чтобы создавать такие плагины, нужна не только высокая программистская квалификация разработчика, но и хорошие знания в той области, в которой работает программа. Как правило, над графическими и звуковыми фильтрами работают команды из нескольких человек, и большая часть времени уходит не на написание кода, а на математическое моделирование преобразований, реализуемых в плагине.

Но для некоторых программ плагины можно писать и не будучи докой по части эффектов Photoshop и гармонического анализа. Множество энтузиастов пишет свои плагины к популярным пользовательским программам - таким, как известная программа для мгновенного обмена сообщениями Miranda. Откровенно говоря, "Мирандой" без плагинов вообще пользоваться довольно-таки затруднительно. Чтобы написать свой плагин для неё, зачастую достаточно знаний школьника, интересующегося программированием, да и вообще любого человека, для которого программирование - не основная работа, а просто хобби.

Чтобы продемонстрировать вам основные принципы написания плагинов на практике, я расскажу, как написать собственный полнофункциональный плагин для популярного файлового менеджера Total Commander. Почему именно для него? Потому что писать к нему плагины достаточно просто, а сам Total Commander я считаю лучшей из программ этого класса.


Итак, пишем?

Писать плагины можно на любом языке программирования, код на котором можно скомпилировать и скомпоновать в динамическую библиотеку. Но сейчас для простоты и наглядности я буду пользоваться Delphi, поскольку с этим языком может работать практически каждый, кто в школе научился Паскалю. Да и сам Total Commander, как известно, написан именно на Delphi.

Плагины к "Командиру" бывают разные. Очень разные, я бы даже сказал. Всего их, на сегодняшний день, четыре типа: плагины встроенного архиватора, плагины встроенного просмотрщика (Lister'а), расширения файловой системы и контент-плагины. Плагины для архиватора позволяют работать через Total Commander с новыми форматами архивов как с обычными папками, плагины Lister'а позволяют просматривать по нажатию на кнопку F3 файлы новых форматов. Плагины файловой системы позволяют работать со структурированными хранилищами данных как с обычными каталогами и файлами, лежащими на диске. В принципе, они чем-то похожи на архивные плагины. Контент-плагины появились в Total Commander'е сравнительно недавно, начиная с версии 6.50 (на момент написания статьи самой новой была версия 7.01). Они позволяют отображать дополнительную информацию о разных файлах в главном окне программы.

Естественно, разные виды плагинов имеют разные принципы работы и разную внутреннюю структуру. Поэтому для дальнейших действий стоит определиться с тем, какой именно плагин мы с вами будем писать. Все плагины хороши, но лично мне кажется, что лучше всего показать на примере плагин для Lister'а, потому что всех форматов файлов не перечислишь, да и новые постоянно появляются. В общем, написание плагинов к Lister'у - полезный практический навык, который может пригодиться в жизни.

Чтобы особо не мучиться с идеей для нашего плагина, будем писать плагин для показа RTF-файлов. Хоть "Командир" это умеет и без того, сейчас наша цель - научиться, а не сотворить нечто поражающее воображение.


Итак, пишем!

Для того, чтобы писать плагин, нужна сначала некоторая предварительная подготовка. Для начала, наверное, неплохо бы установить Delphi. Если она уже установлена, то удалять и ставить по-новому не надо. Кроме самой Delphi, нужны заголовочные файлы, чтобы экспортировать правильные функции и найти, таким образом, общий язык с Total Commander'ом. Скачать их нужно с сайта Total Commander'а (ghisler.com/plugins.htm), называется это "LS-Plugin writer's guide". Прямая ссылка такая: ghisler.fileburst.com/lsplugins/listplughelp1.5.zip, но она, как видите, содержит в себе номер версии, который может измениться.

Ну вот, если вы всё скачали, то можно переходить непосредственно к действиям. Запустите среду Delphi и в окне создания нового проекта выберите "DLL" (в некоторых версиях Delphi это называется "DLL Wizard", но это не принципиально: главная идея такова, что проект должен быть проектом динамически компонуемой библиотеки).

Один небольшой нюанс: поскольку типов плагинов к TC несколько, им всем принято давать разные расширения. Плагины для Lister'а традиционно имеют расширение WLX. Поэтому лучше заранее сменить расширение выходного файла в настройках проекта.

В разделе uses главного файла проекта нужно добавить модули Windows, поскольку нам понадобятся некоторые типы данных оттуда для экспорта функций нашим плагином. Всего же функций, которые нам необходимо экспортировать, три. Вот как они выглядят:

function ListLoad(ParentWin: thandle; FileToLoad: pchar;
 ShowFlags: integer): thandle; stdcall;
procedure ListCloseWindow(ListWin: thandle); stdcall;
procedure ListGetDetectString(DetectString: pchar;
maxlen: integer); stdcall;

Первая из них вызывает плагин для работы. Её параметры - это дескриптор окна Lister'а (с его помощью мы будем поддерживать связь между окном плагина и окном Lister'а), имя загружаемого файла и комбинацию настроек Lister'а. Но настройки нам пока что не понадобятся, так что на ShowFlags сейчас можно не обращать внимания. Вторая функция (вернее, в терминах Delphi это процедура) вызывается, когда завершается работа приложения, и требуется закрытие окна. Её параметр - тоже дескриптор окна Lister'а. Третья функция позволяет Lister'у определить, может ли плагин правильно разобраться с переданным ему файлом, или лучше его отобразить с помощью какого-то другого плагина. В общем-то, можно обойтись и без второй по счёту функции, поскольку в случае её отсутствия окно плагина будет само уничтожено стандартными средствами Windows API.

Добавьте в проект форму ("File"->"New"->Form) и удалите из файла формы глобальную переменную Form1: TForm1. Особенности написания DLL на Delphi таковы, что глобальные переменные во время этого процесса под запретом. На форму положите компонент RichEdit (он находится на вкладке "Win32" палитры компонентов Delphi). Установите его свойство Align на alClient (для этого нужно использовать окно Object Inspector, находящееся в левой части среды).

Окно нашего плагина не должно иметь рамки и заголовка, иначе окно Lister'а будет выглядеть, мягко говоря, странновато. Поэтому для создания окна мы должны переопределить одну процедуру. В секции Protected класса окна нужно записать следующее:

procedure CreateParams(var Params: TCreateParams); override;

В теле процедуры пишем следующее:

inherited CreateParams(Params);
Params.Style := (WS_CHILD or WS_MAXIMIZE) and
not WS_CAPTION and not WS_BORDER;
Params.WindowClass.cbWndExtra := SizeOf(Pointer);

Первая строчка вызывает унаследованный стандартный метод. Вторая обрезает рамку окна, чтобы оно не выглядывало из окна Lister'а неподобающим образом. Третья резервирует место для того, чтобы мы могли потом уничтожить окно (глобальными переменными пользоваться нельзя - помните?).

Теперь можно перейти к непосредственному заполнению тела экспортируемых функций. Тело процедуры ListDetectString будет коротким:

StrLCopy(DetectString, 'FORCE | EXT="RTF"', MaxLen);

Эта функция копирует строку "FORCE | EXT="RTF"" в переменную DetectString. А строка сообщает Lister'у, что мы будем открывать своим плагином файлы с расширением "RTF" и при этом переопределяем стандартную функциональность просмотрщика.

Тело ListLoad будет уже гораздо более длинным:

Var Form1: TForm1;
begin
 Result := 0;
 try
  if LowerCase(ExtractFileExt(string(FileToLoad))) <> '.rtf'
   then Exit;
  Form1 := TForm1.CreateParented(ListerWin);
  Form1.RichEdit1.Lines.LoadFromFile(string(FileToLoad));
  Form1.Show;
  SetWindowLong(Form1.Handle, GWL_USERDATA, Integer(@Form1));
  PostMessage(Form1.Handle, WM_SETFOCUS, 0, 0);
  Form1.RichEdit1.SetFocus;
  Result := Form1.Handle;
 except
 end;
end;

Переменная Form1 - это и есть, собственно, окно плагина. Мы проверяем расширение подсунутого плагину файла, загружаем его, показываем окно, сохраняем указатель на него, чтобы при случае изничтожить. Потом устанавливаем фокус на наш RichEdit и возвращаем дескриптор окна плагина. Поскольку код находится в DLL-библиотеку, помещаем всё внутрь блока try...except, чтобы сбой плагина не привёл к сбою "командира".

Код ListerCloseWindow таков:

Form1 := Pointer(GetWindowLong(PluginWin, GWL_USERDATA));
Form1.Close;
Form1.Free;

Получаем сохранённый адрес формы, закрываем её и освобождаем память. Нужно только не забыть объявить переменную Form1 и "обернуть" всё, что происходит внутри процедуры try...except'ом.

Ну вот, осталось только откомпилировать плагин и установить его в Total Commander'е. По идее, никаких сложностей с этим быть не должно. Если вы заинтересовались написанием плагинов к TC, то зайдите на сайт русскоязычного сообщества пользователей этой программы wincmd.ru. В разделе "Статьи" вы найдёте материалы по написанию плагинов на Delphi и C++, а в разделе закачек есть полнофункциональные плагины с открытым исходным кодом.

Вадим СТАНКЕВИЧ

Версия для печатиВерсия для печати

Номер: 

33 за 2007 год

Рубрика: 

Азбука программирования
Заметили ошибку? Выделите ее мышкой и нажмите Ctrl+Enter!