DLL, Delphi a FastMM4

vložil Radek Červinka 7. května 2010 23:01

Pravidelní čtenáři již vědí co je FastMM4, pro ty dva zbývající: nejlepší správce paměti, defaultní správce paměti pro Delphi 2005+, detektor chyb v uvolňování a v neposlední řadě náhrada ShareMem alias BorlndMM.dll, což je ve starších verzích Delphi originální možnost pro předávání dat do DLL - což ale předbíháme.

Trocha nutné teorie o DLL

DLL (aka Dynamic Linked Library - dynamická připojovaná knihovna) je jedním ze základních kamenů (nejen) Windows, přičemž hlavním smyslem bylo umožnit programům sdílet programový kód, případně rozšiřovat jejich možnosti. Nebudu to moc rozpitvávat, je o tom dost textů, spíše se zaměřím na některé specifika.

String alias řetězec je v Delphi provozován velmi efektivně. Kromě jiného má implementováno počítání referencí, copy on write (tj. rozkopírování řetězce až při zápisu) a další techniky pro urychlení. Pokud bych na něco zajímavého zapomněl, tak můžete použít komentáře, přece jen řetězce v Delphi jsou trošku magie kompilátoru.

Takové vlastnosti ale potřebují trošku speciální podporu, kterou právě dodává správce paměti. Problémem je, že pokud napíšeme DLL, nelze přímo spojit správce paměti hlavního programu se správcem paměti, který je vkompilován do DLL. Když nic neuděláme, tak jak hlavní program tak DLL bude používat vlastního správce paměti, což by v nejhorším nevadilo, tedy pokud nechceme předávat řetězce, třídy obsahující řetezce nebo záznamy mezi aplikací a DLL.

Na druhou stranu, pokud chceme aby naše DLL používal i někdo bez Delphi, musíme se předávání klasických řetězců vyvarovat a použít buďto PChar nebo ShortString, což je pole znaků, nebo pole znaků. Ale toto teď řešit nebudeme.

To bohužel ještě není úplně vše. Je třeba i definovat volací konvenci.

Volací konvence

Co je to volací konvence? Jedná se o způsob, jak jsou předávány parametry mezi vykonávaným programem a procedurou. V podstatě existuje pět způsobů, ale pro funkce exportované z DLL se primárně používá jen jeden. Liší se pořadím parametrů (tj. zprava doleva PL nebo zleva doprava LP) a tím, kdo uklidí zásobník

  • Fast-Call (register) - LP, primárně používaná konvence v Delphi (první tři parametry se předávají přes registry, zbytek jako Pascal), nejrychlejší, pokud nic neuvedete, tak bude použita tato
  • StdCall - PL, doporučené pro funkce exportované z DLL, standard ve Win32
  • Pascal - LP, standard ve Win16
  • Cdecl - PL, jediná volací konvence, kde se o úklid zásobníku stará volající kód, nikdy jsem nepochopil smysl
  • Safecall - PL, nahradí Delphi výjimky na COM chyby

Nejčastější chybou je použití špatné volací konvence. Správně by to měla být StdCall, některé knihovny v C exportují jako Cdecl.

Poslední věcí než si úkážeme nějaký kód je zmínka o speciálních DLL knihovnách pro Delphi - balíčky (BPK). Ale o tom až někdy příště.

Vlastní DLL

První se rozhodněte, zda DLL bude používána jen z Delphi a C++Builderu, nebo dáte šanci o jiným. Já momentálně předpokládám, že ne - tj. Delphi Only. V takovém případě je vhodné zvážit použití balíčků, ale to teď neřeším.

Uděláme DLL knihovnu, která bude demonstrovat použití řetězců za pomoci FastMM4. Pokud nepoužijeme FastMM (tj. do Delphi 7 bez externího FastMM4) musíme distribuovat i BorlndMM.dll (která zaručí uvedené propojení), což se mi ekluje, nebo můžeme použít FastShareMem, což je jeden unit, který pokud je uveden jako první dokáže propojit správce paměti aplikace a DLL bez uvedené BorlndMM.dll.

To samé jako FastShareMem umí i FastMM, jen budete mít navíc i lepšího správce paměti. Takže konec řečí.

library testdll;

uses
  SimpleShareMem; // pouzije FastMM4 integrovane v Delphi - neni treba borlandmm.dll

procedure TestString(var sTestString:ansistring);  stdcall;
begin
  sTestString := 'string z dll';
end;

exports
  TestString; // pozor na velká a malá písmena!
end.

SimpleShareMem musí být první uvedený unit. Kód uložte pod jménem testdll.dpr a po kompilaci se vytvoří testdll.dll, což je naše rozmilá DLL knihovna, která exportuje jednu a jen právě jednu funkci TestString (pozor na velikost písmen). Pokud by bylo třeba exportovat více, oddělujte jména čárkami.

Statické linkování

Nyní máme DLL, tak ji použijeme. Nejdříve staticky, tj. necháme Delphi ať zařídí co je třeba, my jen řekneme v které knihovně má funkci hledat.

Vytvoříme konzolovou aplikaci (ať se nám to neplete) app.dpr.

program app;

// staticke linkovani
uses
  SimpleShareMem, // <- pouzije FastMM4 integrovane v Delphi
  sysutils;
{$APPTYPE CONSOLE}

const
 lib  = 'testdll.dll';    // jmeno knihovny


procedure TestString(var sTestString:ansistring); // deklarace externi funkce z dll
  stdcall; // volaci konvence
  external lib // v knihovne
  name 'TestString'; // exportovane jmeno

var
 s: ansistring;
begin
try
  TestString(s);
  writeln(s);
  readln;
except
  on e: Exception do
    writeln(e.Message);
end;
end.

SimpleShareMem musí být první uvedený unit. AnsiString je neunicode string, ať nedojde ke zmatení.

Řádky 13 - 16 říkáme linkeru Delphi, že se jedná o externí funkci, kde ji má najít ('testdll.dll'), jak se jmenuje (name 'TestString'), jak ji má volat (StdCall) a jaké má parametry (musí odpovídat).

Zavedení knihovny je automaticky po startu programu a my se o nic nestaráme.

Dynamické linkování

Statické linkování je pohodlné, ale taková knihovna je v paměti po celou dobu, což v případě, že ji potřebujeme jen někdy v určité části programu není ideální. Tehdy je výhodné použít dynamické linkování. Pro představu: určitě znáte EasterEgg v Delphi - v about boxu držte ALT a napište TEAM, což zobrazí seznam autorů (mimochodem těch slov je možno zkusit více). Ve starších verzích Delphi seznam vypadal jako úvodní titulky hvězdných válek a byl dělán v OpenGL, které se dotáhlo právě v okamžiku zobrazení tohoto vajíčka, takže do té doby nezabíralo v paměti nic.

Implementačně je to krapet složitější, ale ne zas tak moc. Zato máte plnou kontrolu nad zaváděním a uvolňováním. Použijeme stejnou DLL, ale program bude jiný - soubor dynapp.dpr .

program dynapp;

// dynamicke linkovani
uses
  SimpleShareMem, // <- pouzije FastMM4 integrovane v Delphi
  sysutils, Windows;
{$APPTYPE CONSOLE}

const
 lib  = 'testdll.dll';    // jmeno knihovny

type
  // prototyp funkce z DLL - typ procedura
  TTestStringProc = procedure (var sTestString:ansistring); stdcall;


var
 s: ansistring;
 TestString: TTestStringProc; // vlastni funkce
 hDLLHandle: HModule; // handle pro DLL

begin
try
  hDLLHandle := LoadLibrary(lib); //zaved DLL
  if hDLLHandle <> 0 then
  begin
    try
      // najdi exportovanou funkci
      TestString := GetProcAddress(hDLLHandle, 'TestString');
      if Assigned(TestString) then // povedlo se?
      begin
        TestString(s); //získej řetezec
        writeln(s);
      end
      else
        writeln('Knihovna neexportuje pozadovanou proceduru.');
    finally
      FreeLibrary(hDLLHandle);
    end;
  end
  else
    writeln('Nemohu nalezt dll knihovnu '+lib);
  readln;
except
  on e: Exception do
    writeln(e.Message);
end;
end.

Pokud si to vhodně objektově zapouzdříte, tak to není ani moc složité.

Nyní si za pomocí FastMM klidně můžete vytvořit DLL a předávat jí parametry libovolného typu.

Tagy: , , ,

Praxe

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

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.

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