Написание плагинов
Материал из Wiki.qip.ru
Данная статья создана для разработчиков, которые желают написать собственное дополнение (модуль) для QIP Infium. В отличие от QIP 2005, здесь для этих целей существует специальный SDK (от англ. Software Development Kit) — набор модулей/заголовочных файлов/сборок, которые вы можете подключить к своему проекту, чтобы обеспечить ему корректное взаимодействие с ядром Infium.
Каждый корректно написанный плагин для QIP Infium должен придерживаться определённых рекомендаций и правил.
Данная статья содержит техническое руководство по написанию типичного модуля. О правилах, которым должен следовать разработчик, подробнее см. Правила для разработчиков плагинов.
Содержание |
Карта SDK
Описание SDK
Оригинальный SDK написан на том же языке, на котором написан сам QIP — Delphi. Он представляет собою четыре модуля, в которых описываются интерфейсы, структуры и сообщения, используемые плагинами. Этот SDK развивается и поддерживается непосредственно разработчиками QIP, однако существуют сделанные энтузиастами переводы на другие языки программирования (подробнее описаны в разделе Портированные SDK).
Данная статья большей частью содержит примеры на Delphi. Примеры на других языках программирования вы можете найти в соответствующих посвящённых им разделах/статьях.
Файлы SDK
Все описанные в SDK сущности разделены на четыре модуля по предназначению:
-
u_plugin_info.pas— содержит основные интерфейсы, структуры и сообщения, которые будет использовать каждый модуль при взаимодействии с ядром Infium. -
u_plugin_msg.pas— содержит описание единственной структурыTPluginMessage, инкапсулирующей общий вид сообщения, которыми обмениваются между собой ядро и плагин. -
u_common.pas— этот модуль является общим для ядра и плагинов, то есть используется и подключается к обоим проектам. Указатели на структуры, описанные в нём, передаются как параметры к некоторым из сообщений. -
u_lang_ids.pas— содержит константы, использующиеся при запросе у ядра некоторого строкового литерала на текущем языке локализации QIP Infium.
При каждой структуре или константе можно найти поясняющие их предназначение краткие комментарии на английском языке. Они вместе с примером плагина, который поставляется вместе с SDK, представляют собою базовое руководство по созданию собственного дополнения.
Реализация плагина
DLL
Каждый плагин к QIP Infium представляет собою DLL (dynamic link library). Среди функций, экспортируемых библиотекой, должна присутствовать главная, которая и будет вызываться ядром при его инициализации: CreateInfiumPLUGIN. Функция принимает единственный параметр: интерфейс ядра IQIPPluginService и должна возвращать экземпляр класса, реализовавшего интерфейс плагина IQIPPlugin.
Среди разработчиков плагинов для QIP Infium укоренилась традиция реализовывать сам класс плагина, который и будет реализовывать требуемый интерфейс, в отдельном модуле. Тогда исходный код DLL содержит одну лишь функцию и выглядит обычно примерно следующим образом:
library infplugin; uses u_plugin_info in 'u_plugin_info.pas', u_plugin_msg in 'u_plugin_msg.pas', u_common in 'u_common.pas', u_lang_ids in 'u_lang_ids.pas', u_qip_plugin in 'u_qip_plugin.pas'; function CreateInfiumPLUGIN(PluginService: IQIPPluginService): IQIPPlugin; stdcall; begin Result := TQipPlugin.Create(PluginService); end; exports CreateInfiumPLUGIN name 'CreateInfiumPLUGIN'; end.
Здесь TQipPlugin — отдельный класс, описанный в модуле u_qip_plugin.pas и реализующий интерфейс IQIPPlugin. Обычно при разработке на Delphi такой класс наследуется от TInterfacedObject или его потомков, чтобы не реализовывать собственные QueryInterface(), AddRef() и Release(). Впрочем, разработчик совершенно свободен в собственных вариантах реализации.
Типичные ошибки:
- Не включить функцию в список экспортируемых DLL (
exports). - Не указать стандартное соглашение о вызовах
stdcall. Последняя ошибка особенно критична при написании плагина на языке, отличном от Delphi!
Интерфейсы взаимодействия
Взаимообмен сообщениями между плагином и ядром Infium происходит с помощью двух основных интерфейсов:
- IQIPPluginService — интерфейс, предоставляемый ядром, и
- IQIPPlugin — интерфейс, предоставляемый плагином.
Ниже приведено описание функций, включённых в них:
IQIPPluginService
-
procedure OnPluginMessage(var PlugMsg: TPluginMessage);
Посылает ядру заполненное необходимыми параметрами сообщение PlugMsg. При её использовании необходимо принимать во внимание, что функция работает синхронно, то есть управление вашему коду вернётся только после полной обработки сообщения ядром.
-
function PluginOptions(DllHandle: LongInt): pPluginSpecific;
Функция запрашивает у ядра текущие сохранённые настройки плагина, идентифицируемого дескриптором DllHandle. Обычно, кроме как для запроса собственных настроек (используя дескриптор данной DLL), её не используют. Функция возвращает указатель на структуру TPluginSpecific, подробнее описанную ниже.
Обратите внимание, что возвращается именно указатель на тот же блок памяти, который используется ядром. Поэтому нет необходимости в дополнительной процедуре для установки настроек плагина: вы можете сохранить полученную ссылку в отдельную переменную и изменять её непосредственно.
IQIPPlugin
-
procedure OnQipMessage(var PlugMsg: TPluginMessage);
Процедура реакции на сообщение PlugMsg, отправленное ядром плагину.
- Внимание! Ни в коем случае не загружайте этот обработчик длительными операциями и циклами! Вызов процедуры
OnQipMessageтакже происходит синхронно, поэтому все действия ядра Infium останавливаются в ожидании конца обработки плагином сообщения. Лучше всего выполнять долговременную обработку в отдельном потоке. Это также касается модальных диалоговых окон и показа модальным образом форм плагина. перейти к статье по многопоточным плагинам.
Также стоит принять во внимание, что одно и то же сообщение перенаправляется всем остальным плагинам. Поэтому ни в коем случае нельзя изменять его параметры (если сообщение не предполагает обратное) либо использовать как ответное при отправке своего сообщения ядру.
-
function GetPluginInfo: pPluginInfo;
Функция возвращает ядру указатель на структуру типа TPluginInfo, содержащую метаинформацию о плагине.
Обмен сообщениями
Вся информация между ядром и плагинами передаётся сообщениями, которые пересылают друг другу ядро и модуль, вызывая соответствующие методы у интерфейсов друг друга. Структура сообщения описана в модуле u_plugin_msg.pas следующим образом:
type TPluginMessage = record Msg : DWord; WParam : Integer; LParam : Integer; NParam : Integer; Result : Integer; DllHandle : DWord; end;
В ней:
-
Msg— константа «тип сообщения». Все возможные сообщения описаны в модулеu_plugin_info.pasс комментариями по поводу использования. В данной статье вы можете найти этот список в разделе Составляющие SDK.
Все сообщения делятся на две группы: «ядро → плагин» и «плагин → ядро». Особняком стоят сообщения PM_PLUGIN_MESSAGE и PM_PLUGIN_BROADCAST, которые в силу своего предназначения отправляются некоторым плагином ядру, после чего пересылаются другому плагину; таким образом, они могут присутствовать в обработчиках как клиента, так и сервера.
Также можно классифицировать сообщения по предназначению: некоторые из них носят системные функции и уведомляют об успешной инциализации/включении/отключении QIP/плагина и т.д., а остальные реализуют фактическую дополнительную функциональность SDK.
-
WParam,LParam,NParam— параметры сообщения. Их фактическое значение меняется для каждого отдельного типа сообщения. -
Result— результат, который может возвратить ядро/плагин в ответ на некоторые сообщения, если это предусмотрено его типом. Также в отдельных случаях это поле используется как четвёртый информационный параметр, если вместе с сообщением необходимо передать много сведений, которые нелогично объединять в отдельную структуру. -
DllHandle— дескриптор плагина-отправителя сообщения. По нему ядро идентифицирует, к какому плагину относится данное выполняемое действие или запрос. Дескриптор вашего плагина при его инициализации записывается ядром с помощью вызова методаGetPluginInfo()у полученного им интерфейса плагина.
- Внимание! Это поле обязательно должно быть заполнено перед непосредственной отправкой сообщения.
Класс плагина
Класс вашего плагина теоретически может быть реализован как угодно (не забывая о технических ограничениях и реализации интерфейса). Однако среди разработчиков дополнений для QIP Infium установились некоторые традиции, которых сейчас при написании своего модуля придерживается большинство. Практически все они происходят из примера написания плагина, который поставляется вместе с SDK. Ниже описана приблизительная шаблонная структура плагина, который получается, исходя из этих рекомендаций.
Каждый класс должен сохранять ссылку на сервис ядра, чтобы иметь возможность обмениваться с ним сообщениями. Ссылка эта передаётся в функцию CreateInfiumPLUGIN() в DLL при инициализации QIP Infium, откуда чаще всего перенаправляется входным параметром в конструктор класса плагина. Минимальный конструктор в таком случае выглядит как:
constructor TQipPlugin.Create(const PluginService: IQIPPluginService); begin FPluginSvc := PluginService; end;
Часто в конструкторе класса ещё происходит инициализация ресурсов, которые должны существовать на всём протяжении сеанса QIP Infium — к примеру, иконки плагина или языка локализации. В таком случае подобные ресурсы освобождаются в деструкторе класса, а не в соответствующем методе при получении сообщения об отключении дополнения.
Реализация интерфейса IQIPPlugin предусматривает два метода. Из них метод GetPluginInfo() в минимальном варианте состоит из одной строчки и возвращает указатель на поле информации о плагине:
function TQipPlugin.GetPluginInfo: pPluginInfo; begin Result := @FPluginInfo; end;
Однако метод усложняется, если строковые ресурсы в информации о плагине (к примеру, его описание) должны быть локализированы на текущий язык QIP Infium. В таком случае запрос языка и локализированного текста происходит обычно непосредственно в функции GetPluginInfo().
Основные сообщения
Реализация же метода OnQipMessage() требует, чтобы в ней была учтена реакция на все основные сообщения ядра плагину, и вдобавок те, которые ему необходимы для выполнения своего предназначения. К основным сообщениям можно отнести:
-
PM_PLUGIN_LOAD_SUCCESS— сообщение об успешной инициализации Infium. ПараметрыWParamиLParamв таком случае содержат соответственно старший и младший номер версии SDK, которая поддерживается ядром.
- Внимание! При получении этого сообщения плагин должен заполнить структуру
TPluginInfoдля её последующей передачи ядру. Допускается запрос языка текущей локализации, папки профиля и прочей технической информации. Но более никакие активные действия в данный момент проводиться не должны!
-
PM_PLUGIN_RUN— сообщение об успешном запуске. Только теперь плагин имеет право загружать данные, настройки, ресурсы и т.д., начинать своё активное функционирование. Сообщение не отправляется, если при старте программы плагин отключён в настройках у пользователя - то при включении плагина будет получено сообщениеPM_PLUGIN_ENABLE, но неPM_PLUGIN_RUN. -
PM_PLUGIN_ENABLE— сообщение о включении плагина пользователем в настройках QIP Infium. Производимые действия обычно идентичны реакции наPM_PLUGIN_RUN. -
PM_PLUGIN_QUIT— сообщение о выключении Infium. Все ресурсы, используемые плагином, должны быть немедленно освобождены. Также стоит послать ядру команду сохранить настройки плагина.
- Внимание! При получении этого сообщения в плагине должны быть освобождены все задействованные ресурсы, объекты, указатели, чтобы QIP Infium мог корректно завершить свою работу и полностью выгрузиться из памяти. Если QIP Infium некорректно завершит свою работу, могут возникнуть различные проблемы, например, могут не сохраниться ряд настроек пользователя.
- Внимание! Стоит учитывать, что это сообщение будет послано и отключенному плагину.
-
PM_PLUGIN_DISABLE— сообщение об отключении плагина пользователем в настройках QIP Infium. Чаще всего реакция на сообщение не отличается от реакции наPM_PLUGIN_QUIT. Впрочем, иногда сохранение настроек производят только при закрытии программы.
После получения сообщения об отключении плагину более не будет отправлено ни одного сообщения, кроме PM_PLUGIN_ENABLE и PM_PLUGIN_QUIT.
-
PM_PLUGIN_OPTIONS— пользователь нажал кнопку «Настройки» вашего плагина в секции «Модули» интерфейса QIP Infium. Здесь отображается форма настроек, информации о плагине и т.д.
- Внимание! Типичная ошибка — модальный показ окна настроек.
-
PM_PLUGIN_WRONG_SDK_VER— оповещение о том, что версия SDK, использованного в плагине, выше, чем версия SDK, реализованного в ядре. Старший и младший номер последней можно найти в параметрахWParamиLParamсоответственно.
Корректно написанный плагин обязан реагировать на все эти сообщения. Обычно исходный код функции OnQipMessage() состоит из одного оператора case, в котором перебираются все допустимые значения параметра PlugMsg.Msg и вызываются другие методы, выполяющие непосредственно загрузку/освобождение ресурсов, чтение/запись/показ настроек, оповещение об ошибках, а также собственно функциональность плагина.
Остальные аспекты реализации класса, включая необходимые ресурсы, создание, использование и уничтожение форм, и прочий контроль над ходом работы плагина зависит только от разработчика. В качестве класса плагина разработчик может организовать класс-обёртку над сообщениями SDK.
Настройки
Каждый плагин может зарегистрировать в ядре определённый набор собственных настроек: до 10 логических значений, до 10 строк в формате Unicode и до 10 четырёхбайтных целочисленных значений. Это делается с помощью упомянутой выше функции интерфейса ядра PluginOptions(). Соответственно, описание структуры TPluginSpecific, указатель на которую возвращает запрос настроек, взятое из модуля u_common.pas:
type TPluginSpecific = record Bool1 : Boolean; Bool2 : Boolean; Bool3 : Boolean; Bool4 : Boolean; Bool5 : Boolean; Bool6 : Boolean; Bool7 : Boolean; Bool8 : Boolean; Bool9 : Boolean; Bool10 : Boolean; Param1 : DWord; Param2 : DWord; Param3 : DWord; Param4 : DWord; Param5 : DWord; Param6 : DWord; Param7 : DWord; Param8 : DWord; Param9 : DWord; Param10 : DWord; Wstr1 : WideString; Wstr2 : WideString; Wstr3 : WideString; Wstr4 : WideString; Wstr5 : WideString; Wstr6 : WideString; Wstr7 : WideString; Wstr8 : WideString; Wstr9 : WideString; Wstr10 : WideString; end;
Максимальная длина каждой строки не должна превышать 65535 символов после её перекодировки ядром в UTF-8.
Основной проблемой, связанной с настройками плагина, стало то, что данных 30 переменных не всегда хватает для достижения всей возможной гибкости настроек. Поэтому распостранённым решением является использовать в качестве одной из строк TPluginSpecific записанный в одну строковую переменную текст INI или XML файла с настройками дополнения, который может включать уже гораздо большее число пунктов.
Составляющие SDK
Портированные SDK
На сегодняшней день известны попытки портировать SDK для следующих языков программирования: