Написание плагинов

Материал из Wiki.qip.ru

(Перенаправлено с SDK)
Перейти к: навигация, поиск

Данная статья создана для разработчиков, которые желают написать собственное дополнение (модуль) для QIP 2012. В отличие от QIP 2005, здесь для этих целей существует специальный SDK (от англ. Software Development Kit) — набор модулей/заголовочных файлов/сборок, которые вы можете подключить к своему проекту, чтобы обеспечить ему корректное взаимодействие с ядром 2012.

Каждый корректно написанный плагин для QIP 2012 должен придерживаться определённых рекомендаций и правил.
Данная статья содержит техническое руководство по написанию типичного модуля. О правилах, которым должен следовать разработчик, подробнее см. Правила для разработчиков плагинов.

Содержание

Карта SDK

Страница для быстрого поиска связей констант, сообщений, структур, интерфейсов и реализации их в TBaseQipPlugin

Описание SDK

Оригинальный SDK написан на том же языке, на котором написан сам QIP — Delphi. Он представляет собою четыре модуля, в которых описываются интерфейсы, структуры и сообщения, используемые плагинами. Этот SDK развивается и поддерживается непосредственно разработчиками QIP, однако существуют сделанные энтузиастами переводы на другие языки программирования (подробнее описаны в разделе Портированные SDK).

Данная статья большей частью содержит примеры на Delphi. Примеры на других языках программирования вы можете найти в соответствующих посвящённых им разделах/статьях.

Файлы SDK

Все описанные в SDK сущности разделены на четыре модуля по предназначению:

  • u_plugin_info.pas — содержит основные интерфейсы, структуры и сообщения, которые будет использовать каждый модуль при взаимодействии с ядром 2012.
  • u_plugin_msg.pas — содержит описание единственной структуры TPluginMessage, инкапсулирующей общий вид сообщения, которыми обмениваются между собой ядро и плагин.
  • u_common.pas — этот модуль является общим для ядра и плагинов, то есть используется и подключается к обоим проектам. Указатели на структуры, описанные в нём, передаются как параметры к некоторым из сообщений.
  • u_lang_ids.pas — содержит константы, использующиеся при запросе у ядра некоторого строкового литерала на текущем языке локализации QIP 2012.

При каждой структуре или константе можно найти поясняющие их предназначение краткие комментарии на английском языке. Они вместе с примером плагина, который поставляется вместе с SDK, представляют собою базовое руководство по созданию собственного дополнения.

Реализация плагина

DLL

Каждый плагин к QIP 2012 представляет собою DLL (dynamic link library). Среди функций, экспортируемых библиотекой, должна присутствовать главная, которая и будет вызываться ядром при его инициализации: CreateInfiumPLUGIN. Функция принимает единственный параметр: интерфейс ядра IQIPPluginService и должна возвращать экземпляр класса, реализовавшего интерфейс плагина IQIPPlugin.

Среди разработчиков плагинов для QIP 2012 укоренилась традиция реализовывать сам класс плагина, который и будет реализовывать требуемый интерфейс, в отдельном модуле. Тогда исходный код 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!

Интерфейсы взаимодействия

Взаимообмен сообщениями между плагином и ядром 2012 происходит с помощью двух основных интерфейсов:

  • 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 также происходит синхронно, поэтому все действия ядра 2012 останавливаются в ожидании конца обработки плагином сообщения. Лучше всего выполнять долговременную обработку в отдельном потоке. Это также касается модальных диалоговых окон и показа модальным образом форм плагина. перейти к статье по многопоточным плагинам.

Также стоит принять во внимание, что одно и то же сообщение перенаправляется всем остальным плагинам. Поэтому ни в коем случае нельзя изменять его параметры (если сообщение не предполагает обратное) либо использовать как ответное при отправке своего сообщения ядру.

  • 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 2012 установились некоторые традиции, которых сейчас при написании своего модуля придерживается большинство. Практически все они происходят из примера написания плагина, который поставляется вместе с SDK. Ниже описана приблизительная шаблонная структура плагина, который получается, исходя из этих рекомендаций.

Каждый класс должен сохранять ссылку на сервис ядра, чтобы иметь возможность обмениваться с ним сообщениями. Ссылка эта передаётся в функцию CreateInfiumPLUGIN() в DLL при инициализации QIP 2012, откуда чаще всего перенаправляется входным параметром в конструктор класса плагина. Минимальный конструктор в таком случае выглядит как:

constructor TQipPlugin.Create(const PluginService: IQIPPluginService);
begin
  FPluginSvc := PluginService;
end;

Часто в конструкторе класса ещё происходит инициализация ресурсов, которые должны существовать на всём протяжении сеанса QIP 2012 — к примеру, иконки плагина или языка локализации. В таком случае подобные ресурсы освобождаются в деструкторе класса, а не в соответствующем методе при получении сообщения об отключении дополнения.

Реализация интерфейса IQIPPlugin предусматривает два метода. Из них метод GetPluginInfo() в минимальном варианте состоит из одной строчки и возвращает указатель на поле информации о плагине:

function TQipPlugin.GetPluginInfo: pPluginInfo;
begin
  Result := @FPluginInfo;
end;

Однако метод усложняется, если строковые ресурсы в информации о плагине (к примеру, его описание) должны быть локализированы на текущий язык QIP 2012. В таком случае запрос языка и локализированного текста происходит обычно непосредственно в функции GetPluginInfo().

Основные сообщения

Реализация же метода OnQipMessage() требует, чтобы в ней была учтена реакция на все основные сообщения ядра плагину, и вдобавок те, которые ему необходимы для выполнения своего предназначения. К основным сообщениям можно отнести:

  • PM_PLUGIN_LOAD_SUCCESS — сообщение об успешной инициализации 2012. Параметры WParam и LParam в таком случае содержат соответственно старший и младший номер версии SDK, которая поддерживается ядром.
Внимание! При получении этого сообщения плагин должен заполнить структуру TPluginInfo для её последующей передачи ядру. Допускается запрос языка текущей локализации, папки профиля и прочей технической информации. Но более никакие активные действия в данный момент проводиться не должны!
  • PM_PLUGIN_RUN — сообщение об успешном запуске. Только теперь плагин имеет право загружать данные, настройки, ресурсы и т.д., начинать своё активное функционирование. Сообщение не отправляется, если при старте программы плагин отключён в настройках у пользователя - то при включении плагина будет получено сообщение PM_PLUGIN_ENABLE, но не PM_PLUGIN_RUN.
  • PM_PLUGIN_ENABLE — сообщение о включении плагина пользователем в настройках QIP 2012. Производимые действия обычно идентичны реакции на PM_PLUGIN_RUN.
  • PM_PLUGIN_QUIT — сообщение о выключении 2012. Все ресурсы, используемые плагином, должны быть немедленно освобождены. Также стоит послать ядру команду сохранить настройки плагина.
Внимание! При получении этого сообщения в плагине должны быть освобождены все задействованные ресурсы, объекты, указатели, чтобы QIP 2012 мог корректно завершить свою работу и полностью выгрузиться из памяти. Если QIP 2012 некорректно завершит свою работу, могут возникнуть различные проблемы, например, могут не сохраниться ряд настроек пользователя.
Внимание! Стоит учитывать, что это сообщение будет послано и отключенному плагину.
  • PM_PLUGIN_DISABLE — сообщение об отключении плагина пользователем в настройках QIP 2012. Чаще всего реакция на сообщение не отличается от реакции на PM_PLUGIN_QUIT. Впрочем, иногда сохранение настроек производят только при закрытии программы.

После получения сообщения об отключении плагину более не будет отправлено ни одного сообщения, кроме PM_PLUGIN_ENABLE и PM_PLUGIN_QUIT.

  • PM_PLUGIN_OPTIONS — пользователь нажал кнопку «Настройки» вашего плагина в секции «Модули» интерфейса QIP 2012. Здесь отображается форма настроек, информации о плагине и т.д.
Внимание! Типичная ошибка — модальный показ окна настроек.
  • 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 для следующих языков программирования:

См. также

Личные инструменты