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ář.
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".
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:
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.
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)
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.
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.