Принцип создания плагинов в Delphi (исходники)

Источник: Delphirus
rolcom

Иногда нужные мысли приходят после того, как программа сдана заказчику. Для этого придумали плагины. плагины - это простая 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, лежащей в папке ...Delphi5BIN .

Загрузка плагина

Перейдём к теоретической части.

Если плагин это dll, значит её можно подгрузить следующими способами:

  • Прищипыванием её к программе!

function PluginType : PChar; external 'myplg.dll';
 // в таком случае dll должна обязательно лежать возле exe и мы не можем передать 
 // туда конкретное имя! не делать же все плагины одного имени! это нам не подходит. 
 // Программа просто не загрузится без этого файла! Выдаст сообщение об ошибке. 
 // Этот способ может подойти для поддержки обновления вашей программы! 
  • Динамический

это означает, что мы грузим её так, как нам надо! Вот пример:

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

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

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

Функции:

 //как вы поняли загружает dll и возвращает её хендл 
function LoadLibrary(lpLibFileName : Pchar):THandle;
 // пытается найти обработчик в переданной ей хендле dll, 
 // при успешном выполнении возвращает указатель обработчика. 
function GetProcAddress(Module: THandle; ProcName: PChar): TFarProc 
 //освобождает память, занитую dll 
function FreeLibrary(LibModule: THandle);

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

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

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

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
     { Private declarations } 
     //лист, в котором мы будем держать имена файлов плагинов 
    PlugList : TStringList;
     //Процедура загрузки плагина 
    procedure LoadPlug(fileName : string);
     //Процедура инициализации и выполнения плагина 
    procedure PlugClick(sender : TObject);
  public
     { Public declarations } 
end;

var
Form1: TForm1;

implementation
 {$R *.DFM} 

Процедура загрузки плагина. Здесь мы загружаем, вносим имя dll в список и создаём для него пункт меню; загружаем из dll картинку для пункта меню

procedure TForm1.LoadPlug(fileName: string);
var
   //Объявление функции, которая будет возвращать имя плагина 
  PlugName : function : PChar;
   //Новый пункт меню 
  item : TMenuItem;
   //Хендл dll 
  handle : THandle;
   //Объект, с помощью которого мы загрузим картинку из dll 
  res :TResourceStream;
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;
   //Имя dll 
  FileName : string;
   //Хендл dll 
  handle : Thandle;
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
   //Мы указали реакцию на этот тип 
  Plugintype := 'CANVAS';
end;

function PluginName:Pchar;
begin
   //Вот оно, название плагина. Эта строчка будет в менюшке 
  PluginName := '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;
  PluginExec:=True;
end;

exports
  PluginType, PluginName, PluginExec;

end.

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

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

Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=10443