Принцип создания плугинов в Delphi

Материал из DRKB


Иногда нужные мысли приходят после того, как программа сдана заказчику. Для этого придумали плагины. Плагины - это простая dll библиотека, в которой обязательно присутствует ряд процедур и функций, которые выполняют определенные разработчиком действия, например (из моей практики):

  • function PluginType: PChar; - функция, определяющая назначение плагина.
  • function PluginName: PChar; - функция, которая возвращает название плагина. Это название будет отображаться в меню.
  • function PluginExec(AObject: ТТип): Boolean; - главный обработчик, выполняет определённые действия и возвращает TRUE;

и ещё, я делал res файл с небольшим битмапом и компилировал его вместе с плагином, который отображался в меню соответствующего плагина. Откомпилировать res фaйл можно так:

1. создайте файл с расширением *.rc
2. напишите в нём:
bitmap RCDATA LOADONCALL 1.bmp

где

  • bitmap - это идентификатор ресурса
  • RCDATA LOADONCALL - тип и параметр
  • 1.bmp - имя локального файла для компиляций
3. откомпилируйте этот файл программой brcc32.exe, лежащей в папке ...\Delphi5\BIN\ .


Загрузка плагина[править | править код]

Перейдём к теоретической части. Раз плагин это dll значит её можно подгрузить следующими способами:

1. Прицепляем её к программе!

function PluginType: PChar; external 'myplg.dll';

в таком случае dll должна обязательно лежать возле exe и мы не можем передать туда конкретное имя! не делать же все плагины одного имени! это нам не подходит. Программа просто не загрузится без этого файла! Выдаст сообщение об ошибке. Этот способ может подойти для поддержки обновления вашей программы!


2. Динамический. Это означает, что мы грузим её так, как нам надо! Вот пример:

var
  PluginType: function: PChar; // объявляем процедурный тип функции из плагина
  PlugHandle: THandle; //объявляем переменную типа хендл в которую мы занесём хендл плагина

procedure Button1Click(Sender: TObject);
begin
  PlugHandle := LoadLibrary('MYplg.DLL'); // грузим плагин
  if PlugHandle <> 0  then // Получилось или нет??
  begin
    @PluginType := GetProcAddress(plugHandle,'Plugintype'); // ищем функцию в dll
    if @PluginType <> nil then ShowMessage(PluginType); // вызываем функцию
  end;
  FreeLibrary(LibHandle);//освобождаем библиотеку
end;


Вот этот способ больше подходит для построения плагинов!

Функции:

function LoadLibrary(lpLibFileName: PChar): THandle;

// как вы поняли  загружает dll и возвращает её хендл
function GetProcAddress(Module: THandle; ProcName: PChar): TFarProc

// пытается найти обработчик в переданной ей хендле dll,
// при успешном выполнении возвращает указатель обработчика.
function FreeLibrary(LibModule: THandle); // освобождает память, занятую dll


Самое сложное в построений плагинов, это не реализация всего кода, а предусмотрение всего, для чего в программе могут они понадобиться! То есть предусмотреть все возможные типы плагинов! А это не так просто.

Вот полноценный пример реализации простой программы для поддержки плагинов...


Исходный текст модуля программы:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, menus,
  Grids, DBGrids;

type
  TForm1 = class(TForm)
    MainMenu1: TMainMenu;
    N1231: TMenuItem; // меню,  которое будет содержать ссылки на плагины
    procedure FormCreate(Sender: TObject);
  private
    PlugList : TStringList; // лист, в котором мы будем держать имена файлов плагинов
    procedure LoadPlug(fileName: string); // Процедура загрузки плагина
    procedure PlugClick(Sender: TObject);
    // Процедура инициализации и выполнения плагина
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

{ Процедура загрузки плагина. Здесь мы загружаем, вносим имя dll в список
  и создаём для него пункт меню; загружаем из dll картинку для пункта меню }
procedure TForm1.LoadPlug(FileName: string);
var
  PlugName: function: PChar; // Объявление функции, которая будет возвращать имя плагина
  item: TMenuItem;  // Новый пункт меню
  handle: THandle;  // Хендл dll
  res: TResourceStream;  // Объект, с помощью которого мы загрузим картинку из dll
begin
  item := TMenuItem.Create(MainMenu1);  // Создаём новый пункт меню
  handle := LoadLibrary(PChar(FileName));  // загружаем dll
  if handle <> 0 then  // Если удачно, то идём дальше...
  begin
    @PlugName := GetProcAddress(handle, 'PluginName');  // грузим процедуру
    if @PlugName <> nil then
      item.Caption := PlugName
    else
    // Если всё прошло, идём дальше...
    begin
      ShowMessage('dll not identifi ');  // Иначе, выдаём сообщение об ошибке
      Exit;  // Обрываем процедуру
    end;
    PlugList.Add(FileName);  // Добавляем название dll
    res := TResourceStream.Create(handle, 'bitmap', rt_rcdata);  // Загружаем ресурс из dll
    res.SaveToFile('temp.bmp'); // Сохраняем в файл
    res.Free;

    item.Bitmap.LoadFromFile('Temp.bmp');  // Загружаем в пункт меню
    FreeLibrary(handle);  // Уничтожаем dll
    item.onClick := PlugClick;  // Даём ссылку на обработчик
    Mainmenu1.Items[0].Add(item);  // Добавляем пункт меню
  end;
end;

{ Процедура выполнения плагина. Здесь мы загружаем, узнаём тип и выполняем }
procedure TForm1.PlugClick(Sender: TObject);
var
  PlugExec : function(AObject: TObject): Boolean; // Объявление функции, которая будет выполнять плагин
  PlugType : function: PChar;  // Объявление функции, которая будет возвращать тип плагина
  FileName : string;  // Имя dll
  handle   : THandle;  // Хендл dll
begin
  with (Sender as TMenuItem) do
    FileName := PlugList.Strings[MenuIndex]; // Получаем имя dll
  handle := LoadLibrary(PChar(FileName));  // Загружаем dll
  if handle <> 0 then  // Если всё в порядке, то идём дальше
  begin
    // Загружаем функции
    @plugExec := GetProcAddress(handle, 'PluginExec');
    @plugType := GetProcAddress(handle, 'PluginType');
    // А теперь, в зависимости от типа, передаём нужный ей параметр...
    if PlugType = 'FORM' then // Если плагин для формы, то передаём форму
      PlugExec(Form1)
    else
    if PlugType = 'CANVAS' then // Если плагин для канвы, то передаём канву
      PlugExec(Canvas)
    else
    if PlugType = 'MENU' then // Если плагин для меню, то передаём меню
      PlugExec(MainMenu1)
    else
    if PlugType = 'BRUSH' then // Если плагин для заливки, то передаём заливку
      PlugExec(Canvas.Brush)
    else
    if PlugType = 'NIL' then // Если плагину ни чего не нужно, то ни чего не передаём
      PlugExec(nil);
  end;
  FreeLibrary(handle);    // Уничтожаем dll
end;

procedure TForm1.FormCreate(Sender: TObject);
var
 SearchRec: TSearchRec; //Запись для поиска
begin
  plugList := TStringList.Create; // Создаём запись для имён dll'ок
  if FindFirst('*.dll', faAnyFile, SearchRec) = 0 then // ищем первый файл
  begin
    LoadPlug(SearchRec.name); // Загружаем первый найденный файл
    while FindNext(SearchRec) = 0 do
      LoadPlug(SearchRec.name); // Загружаем последующий
    FindClose(SearchRec); // Закрываем поиск
  end;
  // Левые параметры
  Canvas.Font.pitch := fpFixed;
  Canvas.Font.Size := 20;
  Canvas.Font.Style:= [fsBold];
end;

end.


Здесь написан простой исходный текст dll, то есть нашего плагина. Он обязательно возвращает название, тип и выполняет свои задачи

library plug;
uses
  SysUtils, graphics, Classes, windows;

{$R bmp.RES}

function PluginType: PChar;
begin
  Result := 'CANVAS';  // Мы указали реакцию на этот тип
end;

function PluginName:Pchar;
begin
  Result := 'Canvas painter'; // Вот оно, название плагина. Эта строчка будет в менюшке
end;

// Функция выполнения плагина! Здесь мы рисуем на переданной канве анимационную строку.
function PluginExec(Canvas: TCanvas): Boolean;
var
  X     : Integer;
  I     : Integer;
  Z     : Byte;
  S     : string;
  color : Integer;
  proz  : Integer;
begin
  color := 10;
  proz  := 0;
  S := 'hello всем это из плагина ля -- ля';
  for Z:=0 to 200 do
  begin
    proz := proz + 2;
    X := 0;
    for I:=1 to Length(S) do
    begin
      X := X + 20;
      Canvas.TextOut(X, 50, S[i]);
      color  := color + X*2 + Random(Color);
      Canvas.Font.Color := color + X*2;
      Canvas.Font.Color := 10;
      Canvas.TextOut(10, 100, 'execute of ' + IntToStr(proz div 4) + '%');
      Canvas.Font.Color := color + X*2;
      Sleep(2);
    end;
  end;
  Result := True;
end;

exports
  PluginType, PluginName, PluginExec;

end.


Пару советов :

1. Не оставляйте у своих плагинов расширение *.dll, это не катит. А вот сделайте, например *.plu . Просто в исходном тексте плагина напишите {$E plu} Ну и в исходном тексте программы ищите не Dll, а уже plu.
2. Когда вы сдаёте программу, напишите к ней уже готовых несколько плагинов, что бы юзеру было интересно искать новые.
3. Сделайте поддержку обновления через интернет. То есть программа заходит на ваш сервер, узнаёт, есть ли новые плагины или нет, если есть - то она их загружает. Этим вы увеличите спрос своей программы и конечно трафик своего сайта!


Source: www.emanual.ru
ID: 04561