DLL jako plugin

vložil Radek Červinka 11. července 2010 00:13

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.

    1library plug1;
    2
    3uses
    4  SimpleShareMem, // FastMM
    5  SysUtils,
    6  Classes,
    7  frm1 in 'frm1.pas' {Form1};
    8
    9{$R *.res}
   10
   11function ZPluginInfo:shortstring; stdcall;
   12begin
   13  Result := 'Vložení textu v pluginu'; // informacni text
   14end;
   15
   16procedure ZPluginRun(Handle: THandle); stdcall;
   17begin
   18  gShowDialog(Handle);   // zobrazeni
   19end;
   20
   21// exportovane funkce
   22exports
   23  ZPluginInfo, ZPluginRun;
   24
   25begin
   26end.

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:

    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    { Private declarations }
   16  public
   17    { Public declarations }
   18  end;
   19
   20procedure gShowDialog(AppHandle: THandle);
   21
   22implementation
   23
   24{$R *.dfm}
   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.

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
    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    { Private declarations }
   17    procedure mFindPlugins;
   18  public
   19    { Public declarations }
   20  end;
   21
   22type
   23  TPluginInfo = function:shortstring; stdcall; // prototyp identifikacni funkce
   24  TPluginRun = procedure (Handle: THandle); stdcall;  // prototyp spousteci funkce
   25var
   26  Form2: TForm2;
   27
   28implementation
   29{$R *.dfm}
   30
   31procedure TForm2.btnRunClick(Sender: TObject);
   32var
   33  hDLLHandle: HModule; // handle pro DLL
   34  pRunProc: TPluginRun;
   35begin
   36  if lvPlugins.Selected = nil then
   37    Exit;
   38  hDLLHandle := LoadLibrary(PChar(lvPlugins.Selected.Caption)); //zaved DLL
   39  if hDLLHandle <> 0 then
   40  begin
   41    try
   42      pRunProc := GetProcAddress(hDLLHandle, 'ZPluginRun');
   43      if Assigned(pRunProc) then // povedlo se?
   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; // handle pro DLL
   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)); //zaved DLL
   71      if hDLLHandle <> 0 then
   72      begin
   73        try
   74          // najdi exportovanou funkci
   75          pInfo := GetProcAddress(hDLLHandle, 'ZPluginInfo');
   76          if Assigned(pInfo) then // povedlo se?
   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)

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.


Nabízíme Delphi školení a konzultace na různá témata, primárně ve Vaší firmě.

Tagy: , ,

Praxe

Komentáře

13.7.2010 12:53:03 #

Karmil

Pekne, bude aj pokracovanie?

Karmil

13.7.2010 13:03:28 #

radekc

Mohlo by, ale nic mne nenapada. Pokud máte někdo nápad na článek - sem s ním.

radekc

13.8.2010 10:14:13 #

JaroB

Existují i jiné metodologie, jak zacházet s dll jako plugins, případě jak je "snadno" integrovat do delphi aplikace. Mám docela dobré zkušenosti s UIL PlugIn manažerem (jako free sw pouze do verze 5.0) nebo jeho derivátem nxPlugIn (který je vlastně stejným ale fungují mu SetOptions z IDE Delphi dle nové OTA specifikace, platné sice už od Delphi 5, ale vynucované až od Delphi 6), díky nimž se  plugin chová tak jak má - nahraju DLL do specifického adresáře nebo do adresáře aplikace a plugin se zaktivní. Také docela propracovaná metoda je (jako specifický komponent) v knihovně Polaris, což je jistý nadstavbový a rozšiřující balíček k RXLibrary (pokud by měl někdo zájem, mám RXLibrary funkční i pod unicode Delphi s pár novými komponenty).

JaroB

15.8.2010 0:22:48 #

radekc

>RXLibrary funkční i pod unicode Delphi
všechny RX komponenty jsou nyní součástí JVCL a tam jsou i udržované

radekc

20.8.2010 9:36:55 #

JaroB

JVCL je opravdu hustá knihovna, všeobjímající, všezahrnující ale hlavně obrovská. Pro začínající programátory asi fajn. Pro projektové týmy či větší projekty, to si nejsem jist, nemusí být úplně skousnutelná, protože aktualizace meziverzí je vždy problematická.  Já sám jsem rád, že jsem jí vyrazil z velkých projektů. Byl problém jí tam vložit, ale taky byl problém jí vyrazit, mám zcela negativní zkušenosti s knihovnami podobného typu, namátkou třeba knihovna Orpheus od již neexistující TurboPower. Tu jsem si koupil v dobré víře, že mi to pomůže, že to dobře vypadá a má jednotný tvar. Zaplevelil jsem si s tím nové projekty a když TurboPower skončil, tak knihovny zmrzly a já měl problém přejít i na vyšší verzi Delphi. Vyrazit je z projektů mi trvalo několik let. A nejde tady vůbec o to, že jsou k dispozici zdrojáky - prostě objem kódu je tak velký, že přesahuje možnosti jednoho (i dvou) lidí a teď při nástupu unicode verzí Delphi jsou některé úpravy zcela nemyslitelné - můžu použít a upravit tak jednu dvě požadované komponenty, ale ne celý systém...

JaroB

20.8.2010 10:04:06 #

radekc

JVCL je otevřený kód, je opravdu dobře udržovaná a v nových verzích není problém - tam problém nevidím. Je široce používán. Pravda je, že je většinou používám jen JCL.

Ad Orpheus - byl uzavřený projekt, TurboPower jej nakonec uvolnil a stal se z něj Open Source. To je rozdíl. Většina částí knihovny Orpheus je na sourceforge s podporou nových verzí. Viz. uživatel
http://sourceforge.net/users/tpsfadmin a jeho projekty.

radekc

27.7.2011 10:47:51 #

JaroB

Pluginy na bázi UIL jsou nyní nativní součástí RxLibrary viz http://www.micrel.cz/RxLib/dfiles.htm (nebo na torry.net) se spoustou dalších adoptovaných komponent včetně mé foreign-language implementace (plně funkční je ale až od Delphi 2005).

JaroB

Komentování ukončeno

Naše nabídka

Partial English version.

MVP
Ing. Radek Červinka - Embarcadero MVP
profil na linkedin, Twitter:@delphicz

Nabízím placené poradenství a konzultace v oblasti programování a vývoje SW.
Dále nabízíme i vývoj speciálního software na zakázku.

Neváhejte nás kontaktovat (i ohledně reklamy nebo burzy práce).

Pokud chcete podpořit tento server libovolnou částkou, můžete použít PayPal. Moc děkuji.

Delphi Certified Developer

O Delphi.cz

Delphi je jediný moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64 , Mac OSX a na iPhone a Android (s výhledem na další platformy díky FireMonkey) na současném trhu (včetně Windows 8.1).

V současnosti je světová komunita přes dva miliónů vývojářů.

Delphi.cz je nezávislý portál pro uživatele Delphi. Portál není koncipován pro úplné začátečníky, i když i ti se zde nebudou nudit, ale spíše na programátory, kteří již něco znají a chtějí své znalosti dále rozvíjet a sledovat novinky.

Anketa

Poslední komentáře

Comment RSS