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.
1library plug1;
2
3uses
4 SimpleShareMem,
5 SysUtils,
6 Classes,
7 frm1 in 'frm1.pas' ;
8
9
10
11function ZPluginInfo:shortstring; stdcall;
12begin
13 Result := 'Vložení textu v pluginu';
14end;
15
16procedure ZPluginRun(Handle: THandle); stdcall;
17begin
18 gShowDialog(Handle);
19end;
20
21
22exports
23 ZPluginInfo, ZPluginRun;
24
25begin
26end.
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:
1unit frm1;
2
3interface
4
5uses
6 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
7 Dialogs, StdCtrls;
8
9type
10 TForm1 = class(TForm)
11 btn1: TButton;
12 edt1: TEdit;
13 lbl1: TLabel;
14 private
15
16 public
17
18 end;
19
20procedure gShowDialog(AppHandle: THandle);
21
22implementation
23
24
25
26procedure gShowDialog(AppHandle: THandle);
27begin
28 Application.Handle := AppHandle;
29 with TForm1.Create(Application) do
30 begin
31 try
32 ShowModal;
33 finally
34 Free;
35 end;
36 end;
37end;
38end.
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
1unit Unit2;
2
3interface
4
5uses
6 Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
7 Dialogs, StdCtrls, ComCtrls;
8
9type
10 TForm2 = class(TForm)
11 lvPlugins: TListView;
12 btnRun: TButton;
13 procedure FormCreate(Sender: TObject);
14 procedure btnRunClick(Sender: TObject);
15 private
16
17 procedure mFindPlugins;
18 public
19
20 end;
21
22type
23 TPluginInfo = function:shortstring; stdcall;
24 TPluginRun = procedure (Handle: THandle); stdcall;
25var
26 Form2: TForm2;
27
28implementation
29
30
31procedure TForm2.btnRunClick(Sender: TObject);
32var
33 hDLLHandle: HModule;
34 pRunProc: TPluginRun;
35begin
36 if lvPlugins.Selected = nil then
37 Exit;
38 hDLLHandle := LoadLibrary(PChar(lvPlugins.Selected.Caption));
39 if hDLLHandle <> 0 then
40 begin
41 try
42 pRunProc := GetProcAddress(hDLLHandle, 'ZPluginRun');
43 if Assigned(pRunProc) then
44 begin
45 pRunProc(Application.Handle);
46 end;
47 finally
48 FreeLibrary(hDLLHandle);
49 end;
50 end;
51end;
52
53procedure TForm2.FormCreate(Sender: TObject);
54begin
55 mFindPlugins;
56end;
57
58procedure TForm2.mFindPlugins;
59var
60 sr:TSearchRec;
61 hDLLHandle: HModule;
62 pInfo: TPluginInfo;
63 sInfo: string;
64
65
66begin
67 if FindFirst('*.dll', faAnyFile,sr)=0 then
68 begin
69 repeat
70 hDLLHandle := LoadLibrary(PChar(sr.Name));
71 if hDLLHandle <> 0 then
72 begin
73 try
74
75 pInfo := GetProcAddress(hDLLHandle, 'ZPluginInfo');
76 if Assigned(pInfo) then
77 begin
78 sInfo := string(pInfo);
79 with lvPlugins.Items.Add do
80 begin
81 Caption := sr.Name;
82 SubItems.Add(sInfo);
83 end;
84 end;
85
86 finally
87 FreeLibrary(hDLLHandle);
88 end;
89 end;
90 until FindNext(sr)<>0;
91 end;
92 FindClose(sr);
93end;
94
95end.
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.