14 января 2009 г.

Создаём систему плагинов, часть 10

Итак, начинаем писать реализацию.
ВНИМАНИЕ: приводимый здесь и далее код находится в процессе разработки. Не используйте его. В нём могут содержаться ошибки, он может значительно меняться со временем.
Более-менее отлаженную версию я выложу в конце цикла архивом. Часть второстепенных исходников будет рассмотрена и выложена позже.

Во-первых, у нас будут модули с объявлениями интерфейсов. Будут и модули, где эти интерфейсы нам придётся реализовывать. Имеет смысл делать реализацию и декларацию интерфейсов в разных модулях. Почему? На это есть несколько причин.
Декларация интерфейса - это свод правил, согласно которым будет функционировать ваша система плагинов. Этот свод правил будет использоваться как в плагинах, так и в программе-ядре. Кроме того, его же вам нужно будет переводить на другие языки - как минимум на C++. Т.е. один модуль с декларацией интерфейса будет использоваться в куче мест. И совсем ни к чему таскать с ним ещё какой-то "мусор" в виде реализации. Тем более, что реализация - это сугубо частное дело конкретного проекта.
Итак, вспоминаем, что нам понадобится: инициализация модуля плагина, регистрация плагинов, функциональность плагинов. Причём, плагин и ядро представлены у нас одним интерфейсом, поэтому имеет смысл вынести это объявление в отдельный модуль. По этой схеме мы вводим такие модуля:

IntfInit.pas:
unit IntfInit;
(*

Declares entry point for plugin module.
Performs initialization of module.

*)

{$I PluginSystem.inc}

interface

type
IInit = interface
['{2104B774-A54E-4FC2-95DC-145A9877FF30}']
// public
procedure Init(const AInitParams: IInterface); safecall; // late init
procedure Done; safecall; // early done
end;

type
TExportEntryPointProc = function(const AInitParams: IInterface): IInit; safecall; // early init
TExportEntryPointAltProc = function(const AInitParams: IInterface; out Intf: IInit): HRESULT; stdcall; // early init

const
EmportEntryPointName = '_BAEC5854C59C499F8DFE61E5F531E4C6'; // Do Not Localize

(*

1. Every plugin module must export (TExportEntryPointProc):

function GetPluginEntryPoint(const AParams: IInterface): IInit; safecall;
begin
...
end;

exports
GetPluginEntryPoint name EmportEntryPointName;

2. GetPluginEntryPoint can be also declared as following (TExportEntryPointAltProc):

function GetPluginEntryPoint(const AParams: IInterface; out Intf: IInit): HRESULT; stdcall;

*)

implementation

end.


IntfVersion.pas:
unit IntfVersion;
(*

Declares general interface for plugins and core.

*)

{$I PluginSystem.inc}

interface

type
IVersionInfo = interface
['{1D23BA15-6A23-4CFA-8EBE-AD033D2E1AE5}']
// private
function GetGUID: TGUID; safecall;
function GetCaption: WideString; safecall;
function GetDescription: WideString; safecall;
function GetURL: WideString; safecall;
function GetAuthor: WideString; safecall;
function GetVersion: Longword; safecall;

// Returns information about entity
property GUID: TGUID read GetGUID;
property Caption: WideString read GetCaption;
property Description: WideString read GetDescription;
property URL: WideString read GetURL;
property Author: WideString read GetAuthor;
property Version: Longword read GetVersion;
end;

implementation

end.


IntfPlugin.pas:
unit IntfPlugin;
(*

Declares general interfaces for plugins

*)

{$I PluginSystem.inc}

interface

uses
IntfVersion;

type
// Can be requested from InitParams
IRegisterPlugin = interface
['{1A0D621E-01EA-49F5-B35E-8D0E2346D9BA}']
procedure RegisterPlugin(const APlugin: IVersionInfo); safecall;
end;

// Can be requested from InitParams
IPlugins = interface
['{DBF1ECE9-3E34-4F3F-BE36-A8AD3F443974}']
function GetCount: Integer;
function GetItem(const AIndex: Integer): IVersionInfo;

property Count: Integer read GetCount;
property Items[const AIndex: Integer]: IVersionInfo read GetItem; default;
end;

// Optional interface for plugins initialization.
// Should be implemented by plugin if needed.
IPlugin = interface
['{11F486EF-B3D8-4BE1-83A8-AF991D76E056}']
procedure Init(const AParams: IInterface);
procedure Done;
end;

implementation

end.


Плюс нам нужен заголовочник для функциональности плагина. Для примера я реализую очень простую функциональность: текстовый редактор из одного простого Memo позволит вам экспортировать введённый текст в формат, устанавливаемый плагином. Т.е. вы вводите в Memo текст, выбираете плагин и нажимаете "Сохранить". При этом активируется плагин и сохраняет текст. Как - зависит от плагина. Один может показать его на экране. Другой - сохранить в файл. Третий - отправить письмом. Короче, в нашем простом примере вся функциональность плагина будет заключаться в таком простом интерфейсе:

ExportPlugin.pas:
unit ExportPlugin;

{$I PluginSystem.inc}

interface

uses
Windows;

type
IExportText = interface
['{B6C9F516-0C8E-4FEF-A447-FC246FA8F4F2}']
procedure ExportText(const AParentWnd: HWND; const AText: WideString); safecall;
end;

implementation

end.


При этом надо понимать, что первые три файла - это общие заголовочники нашей системы плагинов вообще. Т.е. они будут использоваться в любой вашей программе, которая будет использовать эту систему. Создаёте новую программу - берёте эти файлы и добавляете к ним специфичные для вашей программы. В нашем примере ExportPlugin.pas - это и есть специфичный для нашей программы файл.

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

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

PluginSystem.inc:
{$I jedi.inc}

{$IFNDEF PS_DEFAULT}
{$DEFINE PS_DEFAULT}

{$DEFINE PLUGINSYSTEMDEBUG}

{$ALIGN 4}
{$MINENUMSIZE 4}
{$STACKFRAMES ON}

// Check minimum compiler version
{$IFNDEF BDS5_UP}
{$IFDEF COMPILER5_UP}
{$Message Fatal 'This project requires BDS/Delphi 2007 or above.'}
{$ELSE}
! 'This project requires BDS/Delphi 2007 or above.' !
{$ENDIF}
{$ENDIF}

{$ENDIF ~PS_DEFAULT}


Во-первых, проект будет зависеть от JCL. Извините, ребята, но это обязательный must-have. Далее, у меня есть возможность работать только с D2007/D2009, поэтому в остальных версиях я проверять работу не собираюсь. По этой причине в inc добавлено ограничение на компилятор. Директива PLUGINSYSTEMDEBUG будет включать отладочный код, которым мы будем проверять правильность работы программы. Её можно определить только в inc файле - для включения во всех проектах. Либо же только в опциях проекта (Conditional defines) - для включения только в одном проекте.

Так, с объявлениями мы разобрались. Теперь реализация интерфейсов. Начнём мы с плагинов - там проще. Поскольку реализация общих интерфейсов (тех, что описаны в первых трёх IntfXXX.pas файлах) будет практически одинакова во всех проектах, использующих нашу систему, предлагаю оформить её один раз в виде универсальных модулей, которые потом можно будет просто подключать.

Начнём с реализации для IntfInit.pas. Заметим, что это будет единственный модуль реализации для стороны плагинов (не считая custom-функциональности плагина).

ImplInit.pas:
unit ImplInit;
(*

Implements entry point for plugin.

*)

{$I PluginSystem.inc}

interface

uses
IntfInit;

// During plugin initialization returns AParams passed in IInit.Init
function InitParams: IInterface;

// Register implementation for interface
function RegisterFunctionality(const AGUID: TGUID; const AInterface: IInterface): Boolean;

type
TPostInitProc = procedure(const AParams: IInterface);

var
// You should fill this information at initialization section of your unit
ModuleGUID: TGUID;
ModuleCaption: String;
ModuleDescription: String;
ModuleURL: String;
ModuleAuthor: String;
ModuleVersion: Longword;

OnPostInit: TPostInitProc;

implementation

uses
Windows, IntfInternal, IntfVersion, ImplConsts, SysUtils, Classes;

{$IFDEF PLUGINSYSTEMDEBUG}
procedure OutputDebugString(lpOutputString: PChar); stdcall;
external 'kernel32.dll'
name {$IFDEF UNICODE}'OutputDebugStringW'{$ELSE}'OutputDebugStringA'{$ENDIF};
{$ENDIF PLUGINSYSTEMDEBUG}
function GetProcAddress(hModule: HMODULE; lpProcName: PAnsiChar): Pointer; stdcall; external 'kernel32.dll' name 'GetProcAddress';

// _____________________________________________________________________________

// Init/Done support for package

procedure _Done;
type
TPackageUnload = procedure;
var
PackageUnload: TPackageUnload;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('04 PLUGIN: _Done enter');
{$ENDIF PLUGINSYSTEMDEBUG}
@PackageUnload := GetProcAddress(HInstance, 'Finalize'); //Do not localize
PackageUnload;
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: _Done leave');
{$ENDIF PLUGINSYSTEMDEBUG}
end;

procedure _Init;
type
TPackageLoad = procedure;
var
PackageLoad: TPackageLoad;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('04 PLUGIN: _Init enter');
{$ENDIF PLUGINSYSTEMDEBUG}
@PackageLoad := GetProcAddress(HInstance, 'Initialize'); //Do not localize
PackageLoad;
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: _Init leave');
{$ENDIF PLUGINSYSTEMDEBUG}
end;

// _____________________________________________________________________________

// Unhandled exception support

var
WasError: Boolean;

// Blocks Halt in SysUtils.ExceptHandler
procedure DoneExceptions;
begin
if ExceptObject <> nil then
begin
WasError := True;
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: unhandled exception');
{$ENDIF PLUGINSYSTEMDEBUG}

// Shows error message - from SysUtils.ExceptHandler
ShowException(ExceptObject, ExceptAddr);
// <- here: removed Halt

// Release exception object while we can (memory manager will be shutdowned soon)
TObject(AcquireExceptionObject).Free;
ReleaseExceptionObject;
end;
end;

// _____________________________________________________________________________

// Custom functionality

type
TImplementator = record
GUID: TGUID;
Intf: IInterface;
end;

TImplementators = array of TImplementator;

var
Implementators: TImplementators;

function RegisterFunctionality(const AGUID: TGUID; const AInterface: IInterface): Boolean;
var
Impl: TImplementator;
begin
for Impl in Implementators do
begin
if CompareMem(@Impl.GUID, @AGUID, SizeOf(TGUID)) then
begin
Result := False;
Exit;
end;
end;
SetLength(Implementators, Length(Implementators) + 1);
Implementators[High(Implementators)].GUID := AGUID;
Implementators[High(Implementators)].Intf := AInterface;
Result := True;
end;

// _____________________________________________________________________________

// Entry point

type
TInit = class(TInterfacedObject, IInterface, IInit, IVersionInfo, IInterfaceObjectReference)
private
class var FParams: IInterface;
procedure Init(const AParams: IInterface); safecall;
procedure Done; safecall;
function GetGUID: TGUID; safecall;
function GetCaption: WideString; safecall;
function GetDescription: WideString; safecall;
function GetURL: WideString; safecall;
function GetAuthor: WideString; safecall;
function GetVersion: Longword; safecall;
function GetObject: TObject; safecall;
protected
function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;
function _Release: Integer; stdcall;
{$IFDEF PLUGINSYSTEMDEBUG}
public
constructor Create;
destructor Destroy; override;
{$ENDIF PLUGINSYSTEMDEBUG}
end;

EUnableToUnloadPlugin = class(Exception);

function InitParams: IInterface;
begin
Result := TInit.FParams;
end;

function GetPluginEntryPoint(const AParams: IInterface; out Intf: IInit): HRESULT; stdcall;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: GetPluginEntryPoint enter');
{$ENDIF PLUGINSYSTEMDEBUG}
WasError := False;
Intf := nil;
if not ModuleIsPackage then
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: GetPluginEntryPoint leave');
{$ENDIF PLUGINSYSTEMDEBUG}
Result := HResultFromWin32(ERROR_INVALID_MODULETYPE);
Exit;
end;
TInit.FParams := AParams;
try
// Initialize package
_Init;
except // 'Initialize' calls finalization sections in case of exception =>
// DoneException was called and showed error message.
WasError := True;
// Clear reference to non-existing exception object
// (it was deleted in DoneExceptions) - prevent destructor double-call
AcquireExceptionObject;
ReleaseExceptionObject;
end;
if WasError then
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: _Init failed');
{$ENDIF PLUGINSYSTEMDEBUG}
WasError := False;
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: GetPluginEntryPoint leave');
{$ENDIF PLUGINSYSTEMDEBUG}
Result := HResultFromWin32(ERROR_DLL_INIT_FAILED);
Exit;
end;

// Package was fully initialized.
try
TInit.FParams := nil;
Intf := TInit.Create;
Result := 0;
except
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: exception in GetPluginEntryPoint');
{$ENDIF PLUGINSYSTEMDEBUG}
ShowException(ExceptObject, ExceptAddr);
Result := HResultFromWin32(ERROR_DLL_INIT_FAILED);
end;

if Failed(Result) then
begin
if Intf = nil then
_Done
else
Intf := nil; // _Done called here from _Release
Result := HResultFromWin32(ERROR_DLL_INIT_FAILED);
end;
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: GetPluginEntryPoint leave');
{$ENDIF PLUGINSYSTEMDEBUG}
end;

exports
GetPluginEntryPoint name EmportEntryPointName;

{ TInit }

procedure TInit.Init(const AParams: IInterface);
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('10 PLUGIN: TInit.Init');
{$ENDIF PLUGINSYSTEMDEBUG}
FParams := AParams;
try
// Call event handler
if Assigned(OnPostInit) then
OnPostInit(AParams);
finally
FParams := nil;
end;
end;

function TInit.QueryInterface(const IID: TGUID; out Obj): HResult;
var
Impl: TImplementator;
begin
// Interfaces in TInit
if GetInterface(IID, Obj) then
Result := 0
else
// Interfaces registered via RegisterFunctionality
begin
for Impl in Implementators do
if CompareMem(@Impl.GUID, @IID, SizeOf(TGUID)) then
begin
IInterface(Obj) := Impl.Intf;
Result := 0;
Exit;
end;
Result := E_NOINTERFACE;
end;
end;

function TInit._Release: Integer;

// From System.pas
function InterlockedDecrement(var Addend: Integer): Integer;
asm
MOV EDX,-1
XCHG EAX,EDX
LOCK XADD [EDX],EAX
DEC EAX
end;

begin
Result := InterlockedDecrement(FRefCount);
if Result = 0 then
begin
try
try
Destroy; // Release TInit instance
finally
_Done; // Shutdown the package (all interfaces should be released by now)
end;
except // _Done calls finalization sections =>
// DoneException was called and showed error message.
// Clear reference to non-existing exception object
// (it was deleted in DoneExceptions) - prevent destructor double-call
AcquireExceptionObject;
ReleaseExceptionObject;
end;
// FreeLibrary will be called immediately after exit
end;
end;

procedure TInit.Done;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('10 PLUGIN: TInit.Done');
{$ENDIF PLUGINSYSTEMDEBUG}
end;

function TInit.GetAuthor: WideString;
begin
Result := ModuleAuthor;
end;

function TInit.GetCaption: WideString;
begin
Result := ModuleCaption;
end;

function TInit.GetDescription: WideString;
begin
Result := ModuleDescription;
end;

function TInit.GetGUID: TGUID;
begin
Result := ModuleGUID;
end;

function TInit.GetURL: WideString;
begin
Result := ModuleURL;
end;

function TInit.GetVersion: Longword;
begin
Result := ModuleVersion;
end;

function TInit.GetObject: TObject;
begin
Result := Self;
end;

{$IFDEF PLUGINSYSTEMDEBUG}

constructor TInit.Create;
begin
OutputDebugString('07 PLUGIN: TInit.Create');
inherited;
end;

destructor TInit.Destroy;
begin
OutputDebugString('07 PLUGIN: TInit.Destroy');
inherited;
end;

{$ENDIF PLUGINSYSTEMDEBUG}

initialization
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('05 PLUGIN: ImplInit.pas initialization');
{$ENDIF PLUGINSYSTEMDEBUG}
Implementators := nil;

finalization
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('05 PLUGIN: ImplInit.pas finalization');
{$ENDIF PLUGINSYSTEMDEBUG}
DoneExceptions;
Finalize(Implementators);

end.


Код выглядит запутанным и страшноватым. Что ж, так оно и есть. Но ничего принципиально нового здесь нет - просто собраны в одну кучу часть 2, часть 3, часть 6 и часть 7. Важным психологическим моментом здесь является тот факт, что этот страшный модуль мы написали только один раз. В дальнейшем мы его будем просто подключать в проекты и всю функциональность получать "на халяву".

Давайте отметим ключевые моменты. Во-первых, у нас есть точка входа в плагин - это функция GetPluginEntryPoint, которую мы реализовали согласно уже обсуждённым идеям.
Во-вторых, у нас есть класс, который реализует все интерфейсы плагина: класс TInit. Объект этого класса создаётся в GetPluginEntryPoint, удаляется он по команде из ядра. Перед выгрузкой модуля плагина ядро обязано освободить все интерфейсы плагина, вот оно и освобождает их, а в частности и IVersionInfo нашего объекта.
В-третьих, инициализация пакета происходит в GetPluginEntryPoint, а завершение работы - в TInit._Release. Опять-таки, согласно нашим уже обсуждённым идеям.
Большая часть страшно выглядещего кода отвечает за обработку исключений во всяких неожиданных местах - об этом мы говорили в части 7.
Помимо этого в модуль добавлены RegisterFunctionality и Implementators - это возможность расширить IInit, возвращаемый из GetPluginEntryPoint. Типа, запас на будущее. В нашей демке это использоваться не будет (да и я пока ещё этот функционал не проверял). В реальных программах он будет использоваться, только если вам нужно нацепить какую-то свою custom-функциональность на сам модуль плагина, а не на плагин.
По тексту также расставлены OutputDebugString, отслеживая которые, мы можем проверять правильность работы плагина.
В интерфейсе модуля определены несколько объявлений, используя которые из своего кода плагина, вы сможете кастомизировать модуль плагина. Во-первых это глобальные переменные ModuleXXX. Вы должны заполнить их в секции initialization любого своего модуля. Далее, функция InitParams возвращает вам параметры, которое передаёт плагину ядро при его инициализации. В остальное время функция возвращает nil. Вы можете запросить, например, IVersionInfo ядра программы и проверить его версию - убедиться, что вас загрузила именно ваша программа, а не какая-то другая (с такой же системой плагинов).
Наиболее важным является глобальная процедура OnPostInit. В своей секции initialization вы должны присвоить ей указатель на свою функцию. Она будет вызвана, когда ядро попросит вас проинициализироваться. В этот момент, в частности, вы должны будете вернуть ядру свои плагины.

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

unit PluginUnit0;

{$I PluginSystem.inc}

interface

implementation

uses
Windows, ExceptionsEx, ImplInit, ImplGeneral, IntfVersion, IntfPlugin;

const
MyGUID: TGUID = '{D2C80A06-B935-45D4-8D41-DCA8729F32C0}';

procedure Init;
begin
ModuleGUID := MyGUID;
ModuleCaption := 'Plugin0 Demo module';
ModuleDescription := 'Empty module that does not contain any plugins; written in D2007.';
ModuleURL := 'http://example.com/';
ModuleAuthor := 'Alexeev Alexander';
ModuleVersion := BuildVersion(1);
end;

initialization
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('06 PLUGIN: PluginUnit0.pas initialization'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
Init;

finalization
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('06 PLUGIN: PluginUnit0.pas finalization'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}

end.


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

А вот так выглядит модуль с одним плагином, который ничего не делает:

unit PluginUnit1;

{$I PluginSystem.inc}

interface

implementation

uses
Windows, ExceptionsEx, ImplInit, ImplGeneral, IntfVersion, IntfPlugin;

const
MyGUID: TGUID = '{A54A8948-C6AB-4C65-8E91-C41DE1AB4EB5}';
MyPluginGUID: TGUID = '{65E1FB42-7D48-4207-B8A3-55BA2B32F232}';

type
TTestPlugin = class(TInterfacedObjectEx, IVersionInfo)
protected
function GetGUID: TGUID; safecall;
function GetCaption: WideString; safecall;
function GetDescription: WideString; safecall;
function GetURL: WideString; safecall;
function GetAuthor: WideString; safecall;
function GetVersion: Longword; safecall;
public
constructor Create;
destructor Destroy; override;
end;

{ TTestPlugin }

constructor TTestPlugin.Create;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('11 PLUGIN: PluginUnit1.pas TTestPlugin.Create'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
inherited;
end;

destructor TTestPlugin.Destroy;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('11 PLUGIN: PluginUnit1.pas TTestPlugin.Destroy'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
inherited;
end;

function TTestPlugin.GetAuthor: WideString;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: PluginUnit1.pas TTestPlugin.GetAuthor'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
Result := 'Alexeev Alexander';
end;

function TTestPlugin.GetCaption: WideString;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: PluginUnit1.pas TTestPlugin.GetCaption'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
Result := 'Empty test plugin 1';
end;

function TTestPlugin.GetDescription: WideString;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: PluginUnit1.pas TTestPlugin.GetDescription'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
Result := 'This is plugin that does absolutely nothing; written in D2007.';
end;

function TTestPlugin.GetGUID: TGUID;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: PluginUnit1.pas TTestPlugin.GetGUID'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
Result := MyPluginGUID;
end;

function TTestPlugin.GetURL: WideString;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: PluginUnit1.pas TTestPlugin.GetURL'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
Result := 'http://example.com/';
end;

function TTestPlugin.GetVersion: Longword;
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: PluginUnit1.pas TTestPlugin.GetVersion'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
Result := BuildVersion(1);
end;

var
OldPostInit: TPostInitProc;

procedure PostInit(const AParams: IInterface);
var
I: IVersionInfo;
begin
I := TTestPlugin.Create;
(AParams as IRegisterPlugin).RegisterPlugin(I);
if Assigned(OldPostInit) then
OldPostInit(AParams);
end;

procedure Init;
begin
ModuleGUID := MyGUID;
ModuleCaption := 'Plugin1 Demo module';
ModuleDescription := 'Contains 1 example plugin that does nothing; written in D2007.';
ModuleURL := 'http://example.com/';
ModuleAuthor := 'Alexeev Alexander';
ModuleVersion := BuildVersion(1);
OldPostInit := OnPostInit;
OnPostInit := PostInit;
end;

initialization
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('06 PLUGIN: PluginUnit1.pas initialization'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
Init;

finalization
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString('06 PLUGIN: PluginUnit1.pas finalization'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}

end.


Как видите, мы описали один класс (TTestPlugin), который реализует IVersionInfo - ровно наш плагин. Правда, ничего полезного он делать не умеет - только возвращать информацию о себе. Сам объект создаётся при инициализации модуля - в момент вызова OnPostInit.
Здесь, кстати, видно, что информация о модуле плагина (bpl-ке) и информация о самом плагине - это две разные сущности: одна заполняется глобально на весь проект, вторая - своя для каждого класса.
Чтобы плагин делал что-то полезное, он должен реализовывать дополнительные интерфейсы. Вот, например, как выглядит класс для плагина экспорта, который экспортирует текст показом его на экране (приведена только часть кода, остальной код аналогичен предыдущему):

  ...

TExportPlugin = class(TInterfacedObjectEx, IVersionInfo, IExportText)
protected
// IVersionInfo
function GetGUID: TGUID; safecall;
function GetCaption: WideString; safecall;
function GetDescription: WideString; safecall;
function GetURL: WideString; safecall;
function GetAuthor: WideString; safecall;
function GetVersion: Longword; safecall;

// IExportText
procedure ExportText(const AParentWnd: HWND; const AText: WideString); safecall;
public
constructor Create;
destructor Destroy; override;
end;

...

procedure TExportPlugin.ExportText(const AParentWnd: HWND; const AText: WideString);
begin
{$IFDEF PLUGINSYSTEMDEBUG}
OutputDebugString(' PLUGIN: PluginUnit3.pas TExportPlugin.ExportText'); // Do Not Localize
{$ENDIF PLUGINSYSTEMDEBUG}
MessageBox(AParentWnd, PWideChar(AText), 'Export to MessageBox plugin', MB_OK or MB_ICONINFORMATION);
end;

...


Ну, для начала достаточно. С плагинами мы более-менее разобрались. В следующий раз мы поговорим о сервере.

Комментариев нет :

Отправить комментарий

Можно использовать некоторые HTML-теги, например:

<b>Жирный</b>
<i>Курсив</i>
<a href="http://www.example.com/">Ссылка</a>

Вам необязательно регистрироваться для комментирования - для этого просто выберите из списка "Анонимный" (для анонимного комментария) или "Имя/URL" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку (поддерживается OpenID).

Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.

Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.