ВНИМАНИЕ: приводимый здесь и далее код находится в процессе разработки. Не используйте его. В нём могут содержаться ошибки, он может значительно меняться со временем.
Более-менее отлаженную версию я выложу в конце цикла архивом. Часть второстепенных исходников будет рассмотрена и выложена позже.
Во-первых, у нас будут модули с объявлениями интерфейсов. Будут и модули, где эти интерфейсы нам придётся реализовывать. Имеет смысл делать реализацию и декларацию интерфейсов в разных модулях. Почему? На это есть несколько причин.
Декларация интерфейса - это свод правил, согласно которым будет функционировать ваша система плагинов. Этот свод правил будет использоваться как в плагинах, так и в программе-ядре. Кроме того, его же вам нужно будет переводить на другие языки - как минимум на 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" (для указания вашего имени и (опционально) ссылки на сайт). Все прочие варианты потребуют от вас входа в вашу учётку.
Пожалуйста, по возможности используйте "Имя/URL" вместо "Анонимный". URL можно просто не указывать.
Ваше сообщение может быть помечено как спам спам-фильтром - не волнуйтесь, оно появится после проверки администратором.
Примечание. Отправлять комментарии могут только участники этого блога.