Delphi.cz

Český portál Delphi

DLL jako plugin

Minule jsem ukázal jak se DLL dělá, na co je atd. a dnes zkusíme něco praktického. Zkusíme implementovat do naší aplikace pluginy, tj. rozšiřující moduly.

Náš program zjistí dostupné pluginy, zobrazí informace, co který plugin dělá a po výběru pluginu uživatelem v něm zavolá modální dialog.

DLL

Začneme přímo DLL knihovnou. V Delphi v menu File - New - Other zvolíme Dynamic Link Library a pak dáme ještě nový formulář.

nová DLL

Tímto máme novou DLL s jedním formulářem. Někam si tento projekt uložíme (já pod jménem plug1.dpr, z něho vznikne po kompilaci plug.dll).

No a když už jsme v tom, tak v Project Manageru dáme z popmenu po kliknutí na ProjectGroup přidat další projekt "Add New Project…" a to "VCL forms application".

Project Manager

Ale zpět k DLL. Po zobrazení zdrojového kódu plug1.dpr (popmenu na plug1.dll v Project Manageru) můžeme upravovat kód.

library plug1;

uses
  SimpleShareMem, // FastMM
  SysUtils,
  Classes,
  frm1 in 'frm1.pas' {Form1};

{$R *.res}

function ZPluginInfo:shortstring; stdcall;
begin
  Result := 'Vložení textu v pluginu'; // informacni text
end;

procedure ZPluginRun(Handle: THandle); stdcall;
begin
  gShowDialog(Handle);   // zobrazeni
end;

// exportovane funkce
exports
  ZPluginInfo, ZPluginRun;

begin
end.

Formulář v DLL jsem pro ilustraci navrhl nějak takto:

DLL form

ale je to jen aby bylo co ukázat (mimochodem: mám tam špatně TAB order - viz. červené čísla).

Pokud se tedy podíváme na uvedený výpis, uvidíme (jak a proč viz minulý text o DLL), že naše DLL exportuje jednu funkci (ZPluginInfo) a jednu proceduru (ZPluginRun). Znovu pro jistotu upozorňuji, že záleží přesně na velikosti písmen (tj. ZPluginRun x zpluginrun).

Poslední věc co nám chybí je procedura gShowDialog volaná z ZPluginRun.

Kód formuláře upravíme takto:

unit frm1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    btn1: TButton;
    edt1: TEdit;
    lbl1: TLabel;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

procedure gShowDialog(AppHandle: THandle);

implementation

{$R *.dfm}

procedure gShowDialog(AppHandle: THandle);
begin
  Application.Handle := AppHandle;
  with TForm1.Create(Application) do
  begin
    try
      ShowModal;
    finally
      Free;
    end;
  end;
end;
end.

Byla odstraněna instance formuláře a přidána uvedená procedura, která má jediný zajímavý řádek a to přiřazení Application.Handle := AppHandle; , což je důležité u volání dialogů, kdy nastavuje správně vlastníka vytvářených formulářů.

Tímto máme náš plugin hotov, takže přejdeme na testovací aplikaci.

Aplikace pro test pluginů

Aplikaci máme již jako součást naší projektové skupiny, teď ji jen oživíme. Jen ještě pozn: změnu aktivního projektu v projektové skupině provedeme např. poklepáním a aktivní projekt je zobrazen tučně (viz screenshot projekt manažera).

Formulář v hlavní aplikaci má jen TListView s ViewStyle = vsReport a dvěma sloupci plus tlačítko Run pro spuštění vybraného pluginu.

Testovací formulář

Kód ve formuláři je zajímavější a jedná se o dvě části:

  • při startu zjistit dostupné pluginy
  • spustit plugin po stisku tlačítka
unit Unit2;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, ComCtrls;

type
  TForm2 = class(TForm)
    lvPlugins: TListView;
    btnRun: TButton;
    procedure FormCreate(Sender: TObject);
    procedure btnRunClick(Sender: TObject);
  private
    { Private declarations }
    procedure mFindPlugins;
  public
    { Public declarations }
  end;

type
  TPluginInfo = function:shortstring; stdcall; // prototyp identifikacni funkce
  TPluginRun = procedure (Handle: THandle); stdcall;  // prototyp spousteci funkce
var
  Form2: TForm2;

implementation
{$R *.dfm}

procedure TForm2.btnRunClick(Sender: TObject);
var
  hDLLHandle: HModule; // handle pro DLL
  pRunProc: TPluginRun;
begin
  if lvPlugins.Selected = nil then
    Exit;
  hDLLHandle := LoadLibrary(PChar(lvPlugins.Selected.Caption)); //zaved DLL
  if hDLLHandle <> 0 then
  begin
    try
      pRunProc := GetProcAddress(hDLLHandle, 'ZPluginRun');
      if Assigned(pRunProc) then // povedlo se?
      begin
        pRunProc(Application.Handle);
      end;
    finally
      FreeLibrary(hDLLHandle);
    end;
  end;
end;

procedure TForm2.FormCreate(Sender: TObject);
begin
  mFindPlugins;
end;

procedure TForm2.mFindPlugins;
var
  sr:TSearchRec;
  hDLLHandle: HModule; // handle pro DLL
  pInfo: TPluginInfo;
  sInfo: string;


begin
  if FindFirst('*.dll', faAnyFile,sr)=0 then
  begin
    repeat
      hDLLHandle := LoadLibrary(PChar(sr.Name)); //zaved DLL
      if hDLLHandle <> 0 then
      begin
        try
          // najdi exportovanou funkci
          pInfo := GetProcAddress(hDLLHandle, 'ZPluginInfo');
          if Assigned(pInfo) then // povedlo se?
          begin
            sInfo := string(pInfo);
            with lvPlugins.Items.Add do
            begin
              Caption := sr.Name;
              SubItems.Add(sInfo);
            end;
          end;

        finally
          FreeLibrary(hDLLHandle);
        end;
      end;
    until FindNext(sr)<>0;
  end;
  FindClose(sr);
end;

end.

Metoda mFindPlugins prohledá adresář aplikace postupně na všechny DLL soubory, a při tom zkouší zda se jedná o naši knihovnu, tj. zda tam existuje námi exportovaná funkce. Pokud ano, je zavolána a informace jsou přidány do TListView. Metoda je volána z FormCreate formuláře.

Obsluha tlačítka Run je jednodušší. Zavedeme patřičnou DLL a pokud se v ní nachází naše druhá procedura (resp. pokud knihovna exportuje symbol) tak je zavolána. Doporučuji pojmenovávat patřičné exportní symboly (tj. funkce…) trochu komplikovaněji, ať nedojde k záměně.

Plugin nahrajeme do adresáře k hlavnímu programu a program spustíme. Samozřejmě lze nastavit, aby se jak exe tak dll při kompilaci generovalo do stejného místa (nastavení je v Options projektu - volba Output directory)

Plugin test

Aplikace za běhu

Velikosti souborů

Pokud takto vytvoříme DLL, brzy narazíme na problém velikosti. Delphi do výsledného souboru natlačí všechny potřebné věci z VCL, což ale něco zabere (aplikace je pak ale nezávislá na jiných DLL, které nejsou součástí Windows) - při několika pluginech je to dost patrné. Každý plugin má velikost několika stovek bajtů (např. 600K).

Řešením je překlad s běhovými balíčky (runtime packages). Pokud vhodně zvolíme balíčky, naše pluginy budou malé a zároveň tak můžeme zmenšit velikost hlavní applikace. Pozor: Pokud je soubor přeložen s balíčky, je nutno přidat do seznamu instalovaných souborů i patřičné *.bpl soubory, ve kterých jsou balíčky uloženy.

Runtime package

Zde jsem nechal DLL přeložit se dvěma základními balíčky: vcl a rtl, tj. soubory rtl140.bpl a vcl140.bpl, kde záleží samozřejmě na verzi Delphi. Po překladu má výsledná DLL místo původních >600K cca 22K, což je pěkné.

zdrojáky - verze Delphi 2010, ale by to být s minimálnímu úpravami (ne v kódu, ale v dfm) i ve starších Delphi.

Datum: 2010-07-10 23:13:00 Tagy: DLL, FastMM, začátečníci

Praxe