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.

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.

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

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).

love Delphi

O Delphi.cz

Delphi je moderní RAD nástroj podporující tvorbu nativních aplikací pro platformu Win32, Win64, Mac OSX, Linux a na iPhone a Android.

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.

Poslední komentáře

Comment RSS

Dle měsíců