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.