Image
8.6.2016 0 Comments

Delphi / 1. časť: DLL knižnice

Späť na úvod >> Späť na seriál

Na úvod jedna dobrá správa: práve ste začali čítať prvý z dvanástich článkov druhej vlny Delfinária. Odteraz sa budeme, tak ako kedysi, každý mesiac pravidelne stretávať a zveľaďovať svoje programátorské vedomosti. Želám vám príjemné čítanie.

Dnes sa budeme venovať pomerne dôležitej problematike, ktorou je tvorba a využitie DLL knižníc. Zámerne píšem „pomerne“, pretože ich použitie je do značnej miery závislé od charakteru aplikácie, ktorú práve vyvíjate alebo sa vyvíjať chystáte. Pre niektorých z vás budú DLL knižnice zbytočnosťou, pre iných, naopak, neoceniteľným pomocníkom. Pokiaľ tvoríte aplikácie výlučne v Delphi, budete DLL knižnice vytvárať zrejme iba málokedy. Ak pracujete na projekte, ktorý je vytváraný v niekoľkých programovacích jazykoch, DLL knižnice sa stanú takpovediac vaším každodenným chlebom (samozrejme, platí to iba v prípade, že nedáte prednosť iným riešeniam, napríklad ActiveX komponentom). Pokiaľ už máte základné vedomosti o tom, čo sú DLL knižnice a na čo slúžia, môžete úvodnú pasáž pokojne preskočiť. Ak vám náhodou pojem DLL nič nehovorí, pripravil som pre vás stručný úvod do tejto problematiky. Mimochodom, všetky ukážkové programy sú vytvorené v Delphi 3, mali by však bez problémov fungovať aj vo vyšších verziách.

DLL knižnice a ich poslanie

Vo Windows existujú dva základné typy spustiteľných súborov: EXE súbory a DLL knižnice (podobne ako EXE súbory aj DLL knižnice majú na začiatku súboru písmená MZ). Hlavný rozdiel je v tom, že EXE súbory pracujú ako plnohodnotné aplikácie, zatiaľ čo DLL knižnice predstavujú akúsi zásobáreň funkcií a prostriedkov (resources) používaných rôznymi aplikáciami.

Skratka DLL znamená Dynamic Link Library – dynamicky linkovaná knižnica. Aby sme pochopili, čo tento pojem znamená, musíme si osvojiť aspoň základné informácie o zostavovaní EXE súboru. Proces jeho vytvorenia pozostáva z dvoch hlavných fáz: kompilácie a linkovania. Tie sú vykonávané oddelenými programami, z ktorých prvý sa nazýva kompilátor (compiler) a druhý linker. Ich práca je do značnej miery odlišná. Ako vieme, program sa skladá z viacerých modulov, v Pascale a Delphi nazývaných jednotky (units). Úlohou kompilátora je preložiť tieto jednotky do strojového kódu. V Delphi sa takto preložené jednotky umiestňujú do súborov s príponou DCU (Delphi Compiled Unit). V druhej fáze prichádza na scénu linker, ktorý má za úlohu vytvoriť samotný EXE súbor. Vykoná to tak, že tieto jednotky pospája (zlinkuje) do jediného súboru s príponou EXE a vytvorí tzv. PE hlavičku (PE header), ktorú Windows používajú pri zavádzaní súboru (samozrejme, tento opis je veľmi zjednodušený, v skutočnosti je všetko oveľa komplikovanejšie). Zatiaľ čo kompilátor je závislý od jazyka, linker pracuje výlučne so strojovým kódom (to však neznamená, že s akýmkoľvek linkerom zlinkujete akékoľvek OBJ súbory). Tí z vás, ktorí pracovali v Turbo Pascale, si možno pamätajú direktívu, pomocou ktorej bolo možné k výslednému programu prilinkovať OBJ súbor vytvorený iným prekladačom. Ako príklad môžem uviesť známy súbor MODOBJ.OBJ, ktorý poskytoval funkcie na prehrávanie MOD súborov. Pri statickom linkovaní sa teda spoja preložené (DCU) súbory vášho programu spolu s preloženými súbormi jednotiek, ktoré vaša aplikácia používa. Takéto linkovanie sa nazýva statické – vykoná sa raz, pri zostavovaní programu. Existuje však aj dynamické linkovanie, ktoré sa vykoná až pri zavádzaní či behu programu.

V prípade dynamického linkovania sa zavedie do pamäte požadovaná knižnica. Spôsoby zavádzania sú dva: prvým je zavedenie požadovaných DLL knižníc pred štartom programu; operačný systém ich najskôr nahrá a až potom program spustí. Informácie o tom, aké DLL knižnice sa majú pri štarte programu zaviesť, obsahuje špeciálna sekcia EXE súboru, nazvaná tabuľka importovaných funkcií (import table). Existujú nástroje (TDump, Dependency Walker), pomocou ktorých je možné obsah tejto tabuľky preskúmať a tak zistiť názvy DLL knižníc potrebných na štart programu. Druhým spôsobom je načítanie knižnice za behu programu pomocou Windows API funkcie LoadLibrary. V takomto prípade však, bohužiaľ, uvedené nástroje zlyhajú. Aby sme mohli zistiť, aké knižnice sa zavádzajú prostredníctvom tejto funkcie, museli by sme program debugovať (napr. s Turbo Debuggerom, W32Dasmom či SoftICEom), čo by bolo mimoriadne komplikované.

Jednou z hlavných príčin použitia DLL knižníc je úspora miesta. Predstavte si, že potrebujete použiť jedno dialógové okno v niekoľkých aplikáciách. Nebolo by príliš praktické dávať ho v každej oddelene, veď načo opakovať to isté viackrát? Namiesto toho je vhodné umiestniť toto okno do DLL knižnice a umožniť tak jeho zdieľanie vo viacerých programoch. Ako príklad nám môže poslúžiť štandardné okno na otváranie súborov (zapuzdrené komponentom TOpenDialog), ktoré konieckoncov môžeme použiť aj v Delphi. Toto okno sa nachádza v jednej zo systémových DLL knižníc Windows a aplikácie ho používajú prostredníctvom volania funkcií tejto DLL knižnice.

Výhodou – a zároveň aj nevýhodou – použitia dynamických knižníc je možnosť rozdeliť program na viacero častí. Nevýhodou preto, že ladenie takejto „rozdrobenej“ aplikácie je často dosť problematické. Výhody sa ukážu, keď sa v aplikácii objavia prvé „muchy“: vtedy stačí chybu opraviť v príslušnej DLL knižnici a tú následne odoslať používateľovi, netreba prepravovať celý EXE súbor.

S použitím DLL knižníc dosiahneme úsporu miesta nielen sa hard disku, ale aj v pamäti. Treba však zdôrazniť, že táto úspora platí len vo Windows 95. Príslušná DLL knižnica sa do pamäte nahrá iba raz a programy ju zdieľajú. A to je jedna zo základných príčin nestability Windows 95: ak totiž jedna aplikácia spôsobí haváriu DLL knižnice, všetky ostatné „spadnú“ spolu s ňou. Vo Windows NT sa pre každý proces nahrá knižnica zvlášť (výnimkou sú systémové knižnice). Pamäť sa neušetrí, to je pravda. Ak však knižnica zhavaruje, zrúti sa s ňou iba proces, ktorý ju zaviedol.

Azda najlepším príkladom demonštrujúcim využitie DLL knižníc je multimediálne API DirectX. Je to kolekcia DLL knižníc obsahujúcich množstvo geometrických funkcií na prácu s 3D prostredím, rutiny na mapovanie textúr, prehrávanie zvuku, obsluhu klávesnice, joysticku, myši atď. Predstavte si, že by tieto rutiny boli staticky linkované a každá hra by ich obsahovala v sebe! To by bolo nezodpovedné plytvanie miestom!

Ďalšou výhodou použitia DLL knižníc je možnosť rýchleho upgrade. Tú istú DLL knižnicu môžu využívať novšie verzie programu bez nutnosti rekompilácie. Ako príklad môže poslúžiť knižnica COMCTL32.DLL. Časom je možné pridávať do nej nové funkcie, zatiaľ čo staré aplikácie môžu pracovať bez problémov ďalej. Ako príklad by mohli poslúžiť staršie programy pre Windows 3.1: ak ich spustíte pod Windows 95, budú automaticky využívať možnosti nového používateľského rozhrania. Je to spôsobené tým, že rozhranie majú na starosti systémové knižnice Windows. Tie sa zmeniť môžu, definície funkcií sa však nemenia, a tak ich môžu staršie programy pokojne používať. Tento príklad som však použil iba na ilustráciu, v skutočnosti sa toho pri spustení 16-bitového programu pod Windows 95 či NT deje oveľa viac.

Základom Windows 95 sú tri centrálne bloky: Kernel, User a GDI. V systémovom adresári Windows ich nájdeme pod týmito názvami: KERNEL32.DLL, USER32.DLL a GDI32.DLL. Je tu však jeden zásadný problém: 16-bitové aplikácie nemôžu volať funkcie z 32-bitových knižníc. Preto tu nájdeme aj 16-bitové verzie týchto knižníc: KRNL386.EXE, USER.EXE

a GDI.EXE. Tieto dve skupiny knižníc sa často volajú medzi sebou prostredníctvom mechanizmu zvaného thunk. Veľmi zjednodušene povedané, thunk je akýsi preklad medzi 16- a 32-bitovými volaniami, s jeho pomocou nám pobežia aj 16-bitové programy. Je však potrebné podotknúť, že 16-bitové DLL knižnice nie je možné volať z Delphi. Presnejšie povedané, možné to je, potrebovali by ste však na to thunk prekladač. Najjednoduchšie je zohnať si 32-bitové verzie príslušných knižníc.

Vo Windows NT (ktoré sú úplne 32-bitové) spätnú kompatibilitu so 16-bitovými aplikáciami a programami MS DOS zabezpečuje modul NTVDM.EXE (NT Virtual DOS Machine), čo je vlastne emulátor počítača 486 a systému MS DOS. Vďaka tomuto programu je napríklad možné emulovať 16-bitové aplikácie Windows aj na procesoroch RISC (na týchto procesoroch NTVDM emuluje inštrukcie Intel 486 a zabezpečuje hardvérové virtuálne prostredie). Win16 NTVDM sa nazýva aj WOW (Win16-on-Win32).

To by na úvod hádam aj stačilo. V nasledujúcich odsekoch si povieme o nástroji, ktorý nám dosť pomôže pri pochopení práce s DLL knižnicami.

Dependency Walker

Hneď na úvod musím povedať, že tento nástroj sa nedodáva s Delphi, ale s Visual C++. Pokiaľ ho nevlastníte, môžete túto pasáž pokojne preskočiť a vrátiť sa k nej neskôr. Vrelo vám však odporúčam, aby ste si tento program zohnali, pretože práca s ním je omnoho komfortnejšia ako práca s programom TDump.

Dependency Walker nám umožňuje zistiť, aké DLL knižnice príslušný EXE či DLL súbor vyžaduje. Ešte raz však opakujem, že ani použitím tohto programu nie je možné stopercentne zistiť, ktoré DLL knižnice daná aplikácia po-trebuje (keďže ich môže „naťahovať“ pomocou API funkcie LoadLibrary). Ak v zozname importovaných funkcií nájdeme aj funkciu LoadLibrary, je dosť nepravdepodobné, že sa nám podarí zostaviť presný zoznam DLL knižníc, ktoré daný program používa.

Dajme sa teda do práce. Na mušku si vezmeme súbor DELPHI32.EXE, z menu File zvolíme Open. Ako vidíme, Dependency Walker sa skladá zo štyroch hlavných okien: v prvom z nich vidíme zoznam DLL knižníc, ktorý program „vytiahol“ z tabuľky importovaných funkcií. Keď si niektorú z týchto knižníc vyznačíme, v pravom hornom okne sa nám zobrazí zoznam funkcií, ktoré importuje (vedľa každej je malé písmenko i). Pod týmto oknom sa nachádza zoznam exportovaných funkcií (vedľa každej je malé písmenko e). Úplne naspodku je okno zobrazujúce rôzne dodatočné informácie. Práve z neho je možné vyčítať kompletný zoznam DLL knižníc, ktoré daný EXE súbor či DLL knižnica potrebuje (aj DLL knižnica totiž môže importovať funkcie z iných DLL knižníc). Na prvý pohľad sa môže zdať, že obsah tohto okna a obsah okna vľavo sa nezhodujú. To je však iba zdanie, pretože určitá DLL môže potrebovať inú DLL aj nepriamo. Príklad si ukážeme v nasledujúcom odseku.

Vyznačme si teraz súbor USER32.DLL. Aj táto DLL importuje funkcie z ďalších dvoch knižníc: KERNEL32.DLL a GDI32.DLL. To však neznamená, že potrebuje len tieto dve. KERNEL32.DLL a GDI32.DLL totiž tiež môžu importovať funkcie

z iných knižníc. Aby sme sa dozvedeli názvy všetkých súborov, ktoré USER32.DLL potrebuje, musíme si ho otvoriť (obr. 2). Už sme povedali, že táto knižnica importuje funkcie zo súborov KERNEL32.DLL a GDI32.DLL. Ako vidíme na obrázku, knižnica KERNEL32.DLL neimportuje nijaké funkcie (pozri nasledujúci odsek). Knižnica GDI32.DLL však importuje funkcie z knižníc KERNEL32.DLL a ADVAPI32.DLL. ADVAPI32.DLL zasa importuje funkcie z ERNEL32.DLL. Ako je zrejmé, je to už   celkom slušne rozvinutý strom. Aby sme sa pri jeho prehľadávaní nezbláznili, je tu spodné okno, zobrazujúce kompletný zoznam všetkých potrebných súborov.

V predchádzajúcom odseku sme zistili, že súbor KERNEL32.DLL neimportuje funkcie z nijakej inej DLL. Je to jedna zo základných knižníc operačného systému. Nemôže funkcie importovať, pretože jednoducho nemá odkiaľ – tie najzákladnejšie služby poskytuje práve ona.

Tak, základný kurz obsluhy Dependency Walkera máme za sebou. Na rad prichádza používanie DLL knižníc v Delphi.

DLL v Delphi

Možno my nebudete veriť, ale DLL knižnice sme vo svojich programoch použili niekoľkokrát. Pamätáte sa ešte na článok s názvom Grafika v Delphi (PC REVUE č. 11/1999)? Používali sme v nej API funkcie CreatePolygonRgn, PtInRegion atď. Deklarované sú v jednotke Windows:

function CreatePolygonRgn(const Points; Count, FillMode: Integer): HRGN; stdcall;

function DPtoLP(DC: HDC; var Points; Count: Integer): BOOL; stdcall;

function Polygon(DC: HDC; var Points; Count: Integer): BOOL; stdcall;

function Polyline(DC: HDC; var Points; Count: Integer): BOOL; stdcall;

V sekcii implementation však nájdeme iba strohé odkazy typu

function CreatePolyPolygonRgn; external gdi32 name ‘CreatePolyPolygonRgn’;

function DPtoLP; external gdi32 name ‘DPtoLP’;

function Polygon; external gdi32 name ‘Polygon’;

function Polyline; external gdi32 name ‘Polyline’;

 

Je to logické, pretože ich implementácia sa nachádza v systémových knižniciach Windows. Všimnite si direktívu name. Za normálnych okolností ju nebudete potrebovať. Niektoré prekladače (napr. Borland C++) však majú vo zvyku názov funkcie vyzdobiť rôznymi znakmi, ktoré Delphi takpovediac neuznáva. Napríklad funkcia preložená v Borland C++ môže mať názov @MYPROC$QV. V Delphi takýto názov procedúry nemôžete napísať, preto si jednoducho vymyslíte ľubovoľný a v direktíve name uvediete skutočný názov funkcie.

Všimnite si kľúčové slovo stdcall. Slúži na to, aby program používal tzv. štandardné konvencie volania zásobníka (ktoré sú kompatibilné s Windows). Ak by sme ho tam nedali, Delphi by predpokladalo, že procedúra používa volaciu konvenciu typu register. Rozdiel medzi týmito dvoma konvenciami tkvie v tom, že konvencia register umožňuje odovzdávanie parametrov cez registre (použijú sa maximálne tri: EAX, EDX a ECX). Výhodou takéhoto prístupu je rýchlosť, keďže procesor vie k registrom pristupovať rýchlejšie ako k pamäti. Nevýhodu je, že sa takéto volacie konvencie môžu používať iba v rámci Delphi aplikácií.

Konvencia stdcall používa na odovzdávanie parametrov výlučne zásobník. Takéto riešenie síce je o čosi pomalšie, je však štandardom. Inými slovami, túto direktívu jednoducho musíme používať.

Tvorba DLL knižníc

Pri vytváraní DLL knižníc je najvhodnejšie postupovať tak, že si najskôr vytvoríme program, v ktorom kód odskúšame, a následne ho presunieme do DLL knižnice. Naša prvá DLL knižnica bude obsahovať iba jedinú funkciu, ktorá prevedie reťazec na veľké písmená. Tento príklad je síce nezmyselný, ale ukážeme si na ňom niekoľko zaujímavých skutočností.

Funkcia, ktorá bude umiestnená v DLL knižnici, bude mať nasledujúci tvar:

function NaVelke(co: PChar): PChar;

var tmp:ShortString;

dest:Array[0..30] of char;

begin

 try

   tmp:=StrPas(co);

   tmp:=AnsiUpperCase(tmp);

   StrPCopy(dest,tmp);

   NaVelke:=Dest;

 except

   raise

 end; //except

end;

 

Vašou prvou otázkou zrejme bude, prečo som použil typ pchar, a nie string. Odpoveď je jednoduchá: pri práci s DLL knižnicami si skôr alebo neskôr budete musieť osvojiť prácu s reťazcami zakončenými nulou (null-terminated  strings). Čím skôr sa to naučíte, tým lepšie. Druhým dôvodom je, že Delphi pojem string implicitne interpretuje ako long string. Ak píšeme DLL pracujúce s takýmito „dlhými“ reťazcami, potrebujeme použiť jednotku ShareMem. Aby som sa tomu vyhol, použil som uvedený postup. Aby konverzné rutiny StrPas a StrPCopy fungovali správne, musel som vo funkcii použiť typ ShortString.

Všimnite si výraz

dest:Array[0..30] of char;

Všetky polia, ktorých číslovanie sa začína od nuly, Delphi berie ako premenné typu pchar. Ich použitie je veľkou výhodou, pretože netreba volať procedúry na alokovanie pamäte.

Funkcia je teda hotová, môžeme ju presunúť do DLL knižnice. Procedúru si najskôr skopírujeme do schránky. Z menu File zvolíme New, potom vyberieme položku DLL. Funkciu potom vložíme, pridáme príslušné direktívy a klauzulu exports. Konečná podoba zdrojového kódu DLL knižnice bude takáto:

library PcharDLL;

uses

  SysUtils,

  Classes;

function NaVelke(co: PChar):PChar;stdcall;export;

var tmp:ShortString;

dest:Array[0..30] of char;

begin

 try

   tmp:=StrPas(co);

   tmp:=AnsiUpperCase(tmp);

   StrPCopy(dest,tmp);

   NaVelke:=Dest;

 except

   raise

 end; //except

end;

 

exports NaVelke;

 

begin

end.

Všimnite si klauzulu exports. Pomocou nej prekladač určí, ktoré funkcie z DLL knižnice budú prístupné ostatným aplikáciám. Hoci primárnou funkciou DLL je poskytovať funkcie iným programom, neznamená to, že im sprístupní všetky funkcie, ktoré obsahuje. Treba si uvedomiť, že aj v DLL knižnici môže byť uložený zložitý program, rozdelený na procedúry a funkcie. Niektoré z nich pritom slúžia len na interné účely (t. j. sú zdieľané niekoľkými rutinami DLL-ky) a nemalo by zmysel ich exportovať. Čím viac funkcií totiž knižnica exportuje, tým dlhšie trvá operačnému systému, kým vyhľadá tú potrebnú a zavolá ju.

Po kompilácii má knižnica veľkosť 51 KB (tá sa bude, prirodzene, meniť v závislosti od toho, ktorú verziu Delphi používate). A teraz pozor: z uses klauzuly vymažeme jednotku Classes. Projekt opäť skompilujeme a výsledná knižnica bude menšia! U mňa veľkosť klesla z 51 KB na 37 KB. Viete, čo sa stalo? Jednotka Classes tam bola jednoducho nepotrebná, keďže sme nevyužívali nijaké funkcie obsiahnuté v nej. Pri tvorbe takýchto malých DLL sa oplatí rad-radom prejsť všetky funkcie a zistiť, v ktorej jednotke sa nachádzajú. Tieto jednotky potom pridáme do uses klauzuly, takže náš program nebude odkazovať na nijaké nepotrebné jednotky.

Všimnite si, že som použil blok try...except. V DLL by ste vždy mali zabezpečiť svoj kód týmto blokom (alebo blokom try...finally). Ak sa totiž handler výnimky nenájde v DLL, výnimka DLL knižnicu opustí, v dôsledku čoho ju „zdedí“ volajúci program a problém je na svete.

Teraz je načase napísať program, ktorý naše 32-bitové veľdielo využije.  Pracuje na jednoduchom princípe: zadáme mu reťazec, ktorý prevedie na veľké písmená. Samozrejme, na prevod použije funkciu uloženú v našej DLL knižnici. Aby sme s touto funkciou mohli pracovať, musíme ju najskôr deklarovať:

function NaVelke(co:PChar):PChar;stdcall;external ‘pchardll’;

Nezabudnite na kľúčové slovo stdcall! Program sa vám bez neho síce skompiluje, nebude však pracovať správne.

Ďalej je už potrebné iba vytvoriť procedúru, ktorá nám vykoná prevod:

procedure TForm1.ConvertClick(Sender: TObject);

var res:PChar;

begin

  Res:=NaVelke(PChar(Vstup.text));

  vystup.caption:=StrPas(res);

end;

 

Aby sme neostali iba pri triviálnych rutinách, ukážeme si teraz, ako zahrnúť formulár do DLL knižnice. Ešte predtým však kód upravíme tak, aby už nepotreboval knižnicu PCHARDLL.DLL, keďže konverzia bude súčasťou metódy ConvertClick:

 

procedure TForm1.ConvertClick(Sender: TObject);

begin

  vystup.caption:=AnsiUpperCase(vstup.Text);

end;

 

Keďže výsledná knižnica bude obsahovať formulár, rozhodol som sa ju nazvať FormDLL. Jednotka, v ktorej sa nachádza formulár, ktorý bude umiestnený do DLL knižnice, sa volá KonverziaDLG. Samotná DLL bude vyzerať takto:

 

library FormDLL;

 

uses

  SysUtils,

  Forms,

  konverziaDLG in ‘KonverziaDLG.pas’;

 

procedure ShowDlg;stdcall;export;

begin

  ConvertForm:=TConvertForm.Create(Application);

   try

     ConvertForm.ShowModal;

   finally

     ConvertForm.Free;

   end;//try

end;

 

exports ShowDlg;

 

begin

end.

Ako vidíme, stačilo nám iba pridať do uses klauzuly odkaz na jednotku, ktorá obsahuje potrebný formulár. Novinkou však je, že formulár musíme vyrobiť takpovediac ručne ešte prv, než budeme pristupovať k jeho vlastnostiam či metódam. Na to nám slúži dobre známy konštruktor Create. Až po jeho zavolaní je možné použiť metódu ShowModal.

V tomto prípade nemôžeme použiť metódu Show, pretože by sa formulár iba zobrazil a vzápätí by zmizol.

Na testovanie DLL knižnice nám postačí formulár s jediným tlačidlom, ktoré zavolá procedúru ShowDlg. Nesmieme však zabudnúť na jej deklaráciu:

procedure ShowDlg;stdcall;external ‘formdll’;

Testovacia rutina bude veľmi jednoduchá:

procedure TForm1.Button1Click(Sender: TObject);

begin

  ShowDlg;

end;

Iste ste spozorovali, že bezprostredne po vykonaní metódy ShowModal na paneli úloh (taskbare) pribudne ďalšie tlačidlo (akoby sme spustili ďalšiu aplikáciu). Aby sme tento jav odstránili, musíme nastaviť handle objektu TApplication v DLL knižnici na rovnakú hodnotu, ako má objekt TApplication v aplikácii, ktorá rutinu volá. Preto musíme náš kód mierne prepracovať: procedúra v DLL knižnici bude mať jeden parameter typu THandle. Upravená DLL knižnica teda bude vyzerať takto:

library FormDLL;

 

uses

  SysUtils,Windows,

  Forms,

  konverziaDLG in ‘KonverziaDLG.pas’;

 

{$R *.RES}

 

procedure ShowDlg(AppHandle:THandle);stdcall;export;

begin

  Application.Handle:= AppHandle;

  ConvertForm:=TConvertForm.Create(Application);

  try

     ConvertForm.ShowModal;

   finally

     ConvertForm.Free;

   end;//try

end;

 

exports ShowDlg;

 

begin

end.

 

Samozrejme, bude potrebné prepracovať aj kód testovacieho programu:

 

procedure ShowDlg(AppHandle:THandle);stdcall;external ‘formdll’;

 

implementation

 

{$R *.DFM}

 

procedure TForm1.Button1Click(Sender: TObject);

begin

  ShowDlg(Application.Handle);

end;

 

Po týchto úpravách už bude aplikácia pracovať správne.

Doteraz sme pracovali iba s jednoduchými projektmi. Kód sme odladili v jednoduchej aplikácii, odkiaľ sme ho potom premiestnili do DLL knižnice. Tento prístup však nie je vhodný vždy. Môže sa totiž stať, že v DLL knižnici sa ten istý kód bude správať inak, ako keby bol zahrnutý v klasickej aplikácii. Preto Delphi poskytuje možnosť ladenia DLL knižníc. Postup je nasledujúci: najskôr treba napísať testovací program, ktorý bude funkcie našej DLL-ky využívať. Tento program sa nazýva host application. Potom si otvoríme projekt DLL knižnice, z menu Run vyberieme položku Parameters a do poľa Host Application napíšeme názov testovacieho programu (obr. 6). Potom môžeme DLL knižnicu ladiť ako klasickú aplikáciu.

DLL knižnice a prostriedky

Ako som už spomínal na začiatku článku, okrem kódu môžu DLL knižnice obsahovať aj prostriedky (resources). Podľa môjho názoru je to jediný dôvod na tvorbu DLL v Delphi. Prostriedkov môže byť mnoho a ich uložením do DLL knižnice môžete získať dosť veľa. Azda najvýznamnejším prínosom je možnosť nahrať ich len vtedy, keď ich naozaj potrebujeme. Pamätáte sa ešte na článok o prostriedkoch (PC REVUE č. 4/2000)? Princíp je rovnaký ako pri práci s klasickými projektmi: pomocou direktívy kompilátora zahrnieme do DLL knižnice RES súbor obsahujúci naše prostriedky.

Náš ukážkový program bude pracovať takto: po stlačení tlačidla sa do pamäte nahrá DLL knižnica, ktorá bude obsahovať jednu bitmapu a jeden WAV súbor. Hneď po spustení programu sa zvuk prehrá a obrázok zobrazí. Po uzatvorení formulára sa knižnica

z pamäte odstráni pro-stredníctvom API funkcie FreeLibrary. Pôvodne som do ukážkového programu chcel zahrnúť aj animáciu. Žiaľ, ukázalo sa, že komponent TAnimate nie je schopný „vytiahnuť“ prostriedok z DLL knižnice. Pokiaľ je prostriedok obsiahnutý v EXE súbore aplikácie, všetko je v poriadku. Zdá sa však, že DLL knižnice akosi neznáša (platí pre Delphi 3).

Keďže pracujeme s prostriedkami, musíme si najskôr vytvoriť RC súbor. Na to budú potrebné súbory: DING.WAV a súbor FACTORY.BMP. Najlepšie bude, ak ich prekopírujeme do jedného adresára, aby sme ich mali pohromade.

V prvej fáze musíme vytvoriť skriptový súbor pre Borland Resource Compiler. Nie je to nič zložité:

obrazok BITMAP “c:\res\factory.bmp”

zvuk SOUND “c:\res\ding.wav”

Súbor nazveme PROSTRIEDKY.RC. Samozrejme, pred použitím ho ešte musíme skompilovať:

BRCC32 PROSTRIEDKY.RC

Výsledný súbor PROSTRIEDKY.RES potom prekopírujeme do adresára, v ktorom „sídli“ náš projekt. Nasledujúcim krokom bude vytvorenie DLL knižnice, ktorá bude tieto prostriedky obsahovať. Bude to asi najjednoduchšia DLL knižnica, akú sme kedy videli:

library resDLL;

{$R prostriedky.res}

begin

end.

Pri jej vytváraní som narazil na jeden zaujímavý problém: keď som zmazal klauzulu uses, Delphi mi nedovolilo projekt uložiť. Preto som do nej musel pridať ľubovoľnú jednotku, projekt uložiť a následne klauzulu opäť vymazať. Potom už bolo všetko v poriadku.

Knižnica je teda hotová, teraz nám už ostáva iba napísať testovací program. Bude sa skladať z jedného tlačidla a jedného komponentu TImage, ktorý som nazval Obraz. Keďže budeme pracovať so zvukom, nesmieme zabudnúť pridať do uses klauzuly jednotku mmsystem. Ďalej je potrebné deklarovať premennú, ktorá bude obsahovať handle DLL knižnice:

implementation uses mmsystem;

var LibHandle:THandle;

Tú je potrebné deklarovať hneď za kľúčovým slovom implementation, pretože ju budú používať dve metódy. Prvá z nich ju naplní a druhá ju odovzdá ako parameter funkcii FreeLibrary. Prvá metóda vyzerá v praxi takto:

 

procedure TForm1.NacitClick(Sender: TObject);

begin

  LibHandle:=LoadLibrary(‘resdll.dll’);

  if LibHandle<>0 then nacit.Enabled:=false;

  Obraz.Picture.Bitmap.LoadFromResourceName(LibHandle,’obrazok’);

  PlaySound(‘zvuk’,LibHandle,SND_RESOURCE OR SND_ASYNC);

end;

Všimnite si, že sme pri volaní procedúr LoadFromResourceName a PlaySound nepoužili premennú hInstance, ale LibHandle. Je to z toho dôvodu, že tento parameter definuje handle modulu, v ktorom sa prostriedok nachádza. Keď sme si hovorili o prostriedkoch, používali sme premennú hInstance, pretože práve ona bola handle našej aplikácie. Ak však  prostriedky zavádzame z iného súboru, je potrebné použiť handle, ktorý sme získali funkciou LoadLibrary. Aby som zabránil dvojnásobnému zavedeniu knižnice, program zakáže tlačidlo Nacit v prípade, že je zavedenie DLL-ky úspešné.

Kód na uvoľnenie DLL knižnice z pamäte sa nachádza v obslužnej rutine udalosti OnClose hlavného formulára:

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);

begin

  FreeLibrary(LibHandle);

end;

Iste ste postrehli, že naše knižnice obsahujú aj príkazy begin a end, medzi ktorými nie je nijaký kód. Tieto dva príkazy nám umožňujú napísať inicializačnú rutinu, ktorá sa vykoná po zavedení knižnice. Typicky je to krátky program zabezpečujúci inicializáciu globálnych premenných. Praktické použitie inicializačnej rutiny si ukážeme na našom poslednom príklade – DLL knižnici s prostriedkami. Zakaždým, keď bude knižnica zavedená, program nám zobrazí krátku správu. Nesmieme však zabudnúť pridať uses klauzulu a jednotku dialogs:

library ResDLL;

uses dialogs;

 {$R prostriedky.res}

 

begin

  ShowMessage(‘Knižnica zavedená’);

end.

Týmto sa však exkurzia do sveta prostriedkov nekončí. Keď už sme pri tvorbe DLL knižníc, povieme si niečo o zvláštnom type prostriedku, ktorý nesie informácie o verzii. V tomto prípade však nebude potrebné písať skriptový súbor, pretože Delphi tento typ prostriedku podporuje priamo. Pokusným králikom bude DLL knižnica s prostriedkami – RESDLL.DPR. Z menu Project vyberieme položku Options a následne kartu VersionInfo. Aby sme mohli začať formulár vypĺňať, musíme najskôr zaškrtnúť pole Include version information in project. Potom môžeme jednotlivé položky povypĺňať. Delphi nekontroluje, čo do ktorého poľa napíšete, preto môžete tento formulár vyplniť ľubovoľne. Musím však dodať, že by ste vo vypĺňaní mali dodržiavať určité zaužívané konvencie, ktorých opis nájdete v nápovedi. Informácie o verzii je potom možné zobraziť v Prieskumníkovi (pravým tlačidlom klikneme na položku Vlastnosti a vyberieme kartu Verzia.

Má to vôbec zmysel?

Odpoveď na túto otázku si musíte dať vy. Každý projekt je totiž špecifický a je ťažké stanoviť všeobecne akceptovateľné pravidlá. Napriek tomu sa vám pokúsim dať niekoľko tipov, ktoré vám pomôžu rozhodnúť sa.

Pokiaľ píšete programy výhradne v Delphi, nebudete DLL knižnice potrebovať takmer nikdy. Jednou z hlavných výhod DLL je úspora času – knižnicu raz napíšete a jej funkcie potom používate. V Delphi túto úlohu plnia komponenty. Práca s nimi je oveľa jednoduchšia ako práca s DLL-kami. Trochu iná situácia je s používaním DLL knižníc napísaných v iných programovacích jazykoch: pokiaľ máte k dispozícii príslušnú dokumentáciu, nemali by ste mať žiadne problémy. Kedysi boli populárne DLL-ky s rôznymi šifrovacími funkciami, dnes však existuje mnoho komponentov, s ktorými dosiahnete rovnaký výsledok s menšou námahou. Okrem toho existujú aj ActiveX komponenty, ktoré akoby z oka vypadli Delphi komponentom a práca s nimi je takmer rovnaká.

Efektívnosť použitia DLL knižníc ďalej závisí aj od toho, pre akú platformu program vyvíjate. Ako som už spomínal, vo Windows 95 sa knižnica zavedie iba raz a s pomocou memory-mapped súborov je zdieľaná medzi ostatnými aplikáciami. Ak teda túto knižnicu používa niekoľko programov, je to bezpochyby úspora miesta. To však neplatí pre Windows NT, kde sa knižnica zavedie pre každý proces zvlášť. V tomto prípade je to skôr plytvanie miestom.

Existujú však situácie, v ktorých sa vám DLL knižnice budú hodiť. Najlepšie ich využijete ako „sklad“ prostriedkov, ktoré potom môžete nahrávať počas behu aplikácie. Ďalej môžu byť užitočné vtedy, ak ste sa rozhodli obrátiť na „novú vieru“, napríklad Visual C++, a zasekli ste sa pri nejakom probléme. V tom prípade sa môžete vrátiť do Delphi, rutinu napíšete v Pascale a dáte ju do DLL-ky, ktorú potom použijete vo Visual C++.

Nabudúce

O mesiac budeme hovoriť o ladení aplikácií. Keďže nejde o triviálnu vec, bude táto problematika rozobraná v dvoch článkoch, takže o ladení sa vám bude snívať de facto dva mesiace. Prvý článok bude úvodom do problematiky, po ktorom bude nasledovať rozoberanie ladiacich možností v Delphi 3 (D3 ešte stále používa prekvapujúce množstvo ľudí). Samozrejme, na svoje si prídu aj používatelia Delphi 5, ale až o mesiac neskôr, preto ich prosím o zhovievavosť. Nadnes je to všetko, dovidenia nabudúce.

 

Literatúra:

Cantú, M.: Mistrovství v Delphi 2. Computer Press 1996.


Nechajte si posielať prehľad najdôležitejších správ emailom

Mohlo by Vás zaujímať

Ako na to

Ako zbaviť fotky hmly

08.12.2016 11:59

Hmla alebo dym sú často veľmi kreatívne nástroje. No všetkého veľa škodí. Fotka potom stráca kontrast a v podstate na nej nič nevidieť. Hmlu môžete neraz následnými úpravami odstrániť alebo zredukovať ...

Ako na to

Užitočné SW nástroje

08.12.2016 11:53

AllDup v4.0.3 Určenie: program na vyhľadávanie a odstraňovanie duplicitných súborov Vlastnosti: duplicitné súbory sa vyhľadávajú len na zvolených diskových jednotkách alebo len v rámci vybraných ...

Ako na to

Fotografovanie s bleskom

08.12.2016 11:47

Ak máte moderný fotoaparát so vstavaným alebo externým bleskom, zdá sa vám téma článku triviálna. Jednoducho nastavíte vhodný režim, vyberiete najlepšiu kompozíciu záberu, exponujete a o zvyšok sa už ...

Žiadne komentáre

Vyhľadávanie

Kyocera - prve-zariadenia-formatu-a4-s-vykonom-a3

Najnovšie videá