Manison Softworks

Import funkcí do Delphi
Delphi
Zpět
Domů
E-mail

Přestože jsou spolu s Delphi dodávány desítky unit importující množství funkcí ze systémových knihoven, není tato kolekce vždy zcela úplná a ne vždy nám deklarace od Borlandu musí vyhovovat. V tomto článku si ukážeme, jak importovat funkce nejen ze systémových knihoven do tohoto populárního vývojového prostředí.

Dynamické knihovny, hlavičkové soubory a SDK

Jak víme, tak veškeré funkce, které Windows poskytují programátorům (Windows API), se skrývají v dynamicky linkovaných knihovnách (DLL). K tomu, abychom mohli použít některou z funkcí z DLL v našem programu, musíme znát její jméno, počet a typ jednotlivých parametrů, typ návratové hodnoty a volací konvenci. Microsoft poskytuje ke svým knihovnám dokumentaci jako součást Platform SDK (Software Development Kit) nebo on-line na MSDN (Microsoft Developer Network). Deklarace funkcí v SDK je v jazyce v C, stejně jako v hlavičkových souborech, kterými je SDK doprovázena. Pokud máme dostatek informací o funkci a knihovně, ve které je uložena, můžeme přistoupit k samotné deklaraci.

Shlwapi.pas

Ani v posledních verzích Delphi nejsou k dispozici funkce z knihovny shlwapi (shlwapi.dll, Shell light-weight utility API), a to přesto, že některé z nich jsou velmi užitečné. Pojďme si nyní vytvořit vlastní unit tak, abychom mohli tyto funkce využívat ve svých programech. Podívejme se do SDK nebo MSDN na funkci ColorHLSToRGB, která převádí barvu z barevného modelu HLS (hue-luminance-saturation, odstín-světlost-nasycení) na barevný model RGB (red-green-blue, červená-zelená-modrá).
COLORREF ColorHLSToRGB(WORD wHue, WORD wLuminance, WORD wSaturation);
Funkce má tři parametry typu WORD reprezentující složky odstínu, světlosti a nasycení a návratovou hodnotu typu COLORREF. Typ WORD z Delphi známe, je to celé neznaménkové číslo o šířce šestnácti bitů, proměnné tohoto typu mohou tedy nabývat hodnot 0 až 65535. Co je to ale typ COLORREF? Zapátráme-li v SDK, zjistíme, že datový typ COLORREF je dvaatřicetibitové číslo, které reprezentuje červenou, zelenou a modrou složku barvy. Zapíšeme-li toto číslo v šestnáctkové soustavě, jeho tvar bude následující:
$00bbggrr
kde bb, gg a rr jsou hodnoty modré, zelené a červené složky v rozsahu 0 až 255. Nyní tedy již můžeme napsat deklaraci funkce ColorHLSToRGB tak, jak bude vypadat v Delphi.
function ColorHLSToRGB(wHue, wLuminance, wSaturation: Word): LongWord;
Podíváme-li se nyní do unit Windows.pas, nalezneme tam následující definici typu COLORREF:
type
  DWORD = LongWord;
  COLORREF = DWORD;
  TColorRef = DWORD;
Můžeme tedy rovnou přehledněji napsat
uses Windows;
...
function ColorHLSToRGB(wHue, wLuminance, wSaturation: Word): TColorRef;
Většina typů nalezených v SDK je definovaná právě v unit Windows.pas nebo v unit, jejíž jméno odpovídá hlavičkovému souboru, jehož jméno nalezneme v SDK pod popisem datového typu. Jméno typu můžeme použít buď přesně tak, jak jej nalezneme v dokumentaci (viz např. COLORREF) anebo jako upravené jméno s písmenem T na začátku (TColorRef). Pokud definici typu v žádné unit nenalezneme, nezbývá než si tento typ definovat sami. V tabulce jsou shrnuty nejpoužívanější jednoduché datové typy a typy jim odpovídající v Delphi:
int, INT Integer
UINT Cardinal
DWORD LongWord; DWORD (Windows.pas)
BOOL, BOOLEAN LongBool; BOOL (Windows.pas)
Pozor: Typ BOOLEAN z C není shodný s typem Boolean z Delphi. V tomto případě musíte použít typ LongBool nebo BOOLWindows.pas
HANDLE THandle (Windows.pas)
HWND, HICON, HDC, ... THandle nebo stejný typ z Windows.pas
void, VOID prázdný datový typ, např.
DWORD GetLastError(void); je
function GetLastError: Cardinal;
a void Sleep(DWORD dwMilliseconds); je
procedure Sleep(dwMilliseconds: Cardinal);
V Delphi reprezentuje barvu datový typ TColor z unit Graphics.pas, což je také vlastně číslo o šířce 32 bitů. Nejlepší deklarace naší funkce bude tedy
uses Windows, Graphics;
...
function ColorHLSToRGB(wHue, wLuminance, wSaturation: Word): TColor;
Na konci popisu každé funkce je uvedeno, v jaké dynamické knihovně je přeložena. O funkci ColorHLSToRGB se dozvíme, že se nachází v knihovně shlwapi.dll. Pro import funkce do Delphi použijeme klíčové slovo external:
function ColorHLSToRGB(wHue, wLuminance, wSaturation: Word): TColor;
  external 'shlwapi.dll';
Pokud jste nyní zkusili spustit program používající tuto funkci, pravděpodobně havaroval. Na začátku už jsme se stručně zmínili o volacích konvencích. Delphi běžně používají volací konvenci register (zvanou také fastcall). Při použití tohoto způsobu se většina parametrů uloží do registrů procesoru a volání tak může proběhnout velmi rychle. Funkce Windows API ale používají volací konvenci standard call (existují vyjímky potvrzující pravidlo), při které jsou parametry volající funkcí uloženy na zásobník a odtud jsou volanou funkcí vyzvednuty. Protože funkce se snažila přistoupit do zásobníku pro parametry, které byly ve skutečnosti uloženy v registrech, došlo při volání k porušení ochrany paměti a následné vyjímce. Abychom mohli bez obav funkci použít, musíme k deklaraci připsat direktivu stdcall:
function ColorHLSToRGB(wHue, wLuminance, wSaturation: Word): TColor; 
  stdcall; external 'shlwapi.dll';

Ukazatele, hodnoty a reference

Podívejme se nyní na funkci inverzní k ColorHLSToRGB:
VOID ColorRGBToHLS(COLORREF clrRGB, WORD *pwHue, WORD *pwLuminance,
  WORD *pwSaturation);
Funkce převádí barvu v modelu RGB na barevné složky H, L a S. První parametr je známého typu COLORREF a další tři parametry jsou ukazatele na typ Word (WORD následovaný hvězdičkou). Funkce "má návratovou hodnotu typu void", v Delphi budeme tedy deklarovat proceduru (viz tabulka výše):
procedure ColorRGBToHLS(clrRGB: TColor; pwHue, 
pwLuminance, pwSaturation: PWord); stdcall; external 'shlwapi.dll';
PWord je ukazatel na typ WordWindows.pas:
type PWord = ^Word;
Volání takové funkce pak bude vypadat následovně:
var
  Hue, Luminance, Saturation: Word;
...
ColorRGBToHLS(clRed, @Hue, @Luminance, @Saturation);
Jazyk C neumožňuje na rozdíl od jiných vyšších programovacích jazyků (včetně C++) předávat parametry odkazem, ale jen hodnotou. Tento problém se tedy v jazyce C řeší častým používáním ukazatelů. V Delphi nemáme důvod nevyužít možnosti předávat hodnoty odkazem proto napíšeme deklaraci lépe:
procedure ColorRGBToHLS(clrRGB: TColor; 
  var pwHue, pwLuminance, pwSaturation: Word); stdcall; external 'shlwapi.dll';
Nyní můžeme jednodušeji použít
var
  Hue, Luminance, Saturation: Word;
... 
ColorRGBToHLS(clRed, Hue, Luminance, Saturation);
[Tip: Při převádění systémových barev funkcí ColorRGBToHLS musíte barevnou konstantu (clBtnFace, clScrollBar) nejdříve převést funkcí ColorToRGB. Je to proto, že typ Delphi TColor je ve skutečnosti nadmnožinou Windows API typu COLORREF. Proto je třeba před předáním typu TColor Windows API funkci provést určitou konverzi.]

Protože parametry pwHue, pwLuminance, pwSaturation slouží jako výstup jednotlivých složek, je vhodné použít místo klíčového slova var slovo out abychom předešli případnému varování kompilátoru o pokusu předat funkci neinicializované proměnné:
procedure ColorRGBToHLS(clrRGB: TColor; out pwHue, pwLuminance, pwSaturation: Word); 
  stdcall; external 'shlwapi.dll';
Stejně tak v případě, kdy parametr slouží pouze pro předání dat do funkce, použijeme klíčové slovo const, např. (viz Windows.pas):
function IsRectEmpty(const lprc: TRect): BOOL; stdcall; external 'user32.dll';
[Funkce IsRectEmpty zjistí, zda obsah obdálníku, jehož rozměry jsou funkci předávny v záznamu typu TRect (všimněme si opět změny názvu z RECT na TRect), je nenulový.]

U parametrů, které slouží k přenosu dat oběma směry je na místě zachovat klíčové slovo var.
Ne vždy je ale takovéto nahrazení ukazatelů ideální. Podívejme se na funkci ExtractIconEx z knihovny shell32.dll:
UINT ExtractIconEx(LPCTSTR lpszFile, int nIconIndex,
  HICON *phiconLarge, HICON *phiconSmall, UINT nIcons);
a na odpovídající deklaraci od Borlandu do souboru ShellAPI.pas:
function ExtractIconEx(lpszFile: PChar; nIconIndex: Integer;
  var phiconLarge, phiconSmall: HICON; nIcons: UINT): UINT;
  stdcall; external 'shell32.dll';
Funkce v souboru daném parametrem lpszFile najde počet nIcons velkých a/nebo malých ikon a jejich handly uloží do pole, na jehož první prvek ukazuje parametr phiconLarge a/nebo phiconSmall. Funkce tak, jak je deklarovaná v ShellAPI.pas ale neumožňuje extrahovat jen malé nebo jen velké ikony, v každém případě musíme extrahovat obě velikosti ikon, neboť do parametru předávanému odkazem nelze uložit hodnotu nil. Pokud budeme chtít využít popsané možnosti musíme si napsat vlastní deklaraci této funkce:
type
  PHICON = ^HICON;
  
function ExtractIconEx(lpszFile: PChar; nIconIndex: Integer;
  phiconLarge, phiconSmall: PHICON; nIcons: UINT): UINT;
  stdcall; external 'shell32.dll';
Nebo můžeme částečně zachovat výhody předávání parametrů odkazem a vytvořit několik přetížených verzí funkce s různým typem parametrů:
function ExtractIconEx(lpszFile: PChar; nIconIndex: Integer;
  phiconLarge: PHICON; phiconSmall: PHICON; nIcons: UINT): UINT;
  stdcall; external 'shell32.dll'; overload;
  
function ExtractIconEx(lpszFile: PChar; nIconIndex: Integer;
  out phiconLarge: HICON; phiconSmall: PHICON; nIcons: UINT): UINT; 
  stdcall; external 'shell32.dll'; overload;
  
function ExtractIconEx(lpszFile: PChar; nIconIndex: Integer;
  phiconLarge: PHICON; out phiconSmall: HICON; nIcons: UINT): UINT;
  stdcall; external 'shell32.dll'; overload;
Nyní můžeme použít všechny čtyři kombinace volání:
var
  Large1: array [0..5] of HICON;
  Small2: HICON;
  Large3, Small3: HICON;
  Large4, Small4: HICON;
...
ExtractIconEx(PChar('c:\windows\system\shell32.dll'), 1, @Large3,
  @Small3, 1); 
ExtractIconEx(PChar('c:\windows\system\shell32.dll'), 3, Large1[0],
  nil, 6);
ExtractIconEx(PChar('c:\windows\system\shell32.dll'), 0, nil,
  Small2, 1);
ShellAPI.ExtractIconEx('c:\windows\system\shell32.dll', 1, Large4,
  Small4, 1);	// originalní varianta ze ShellAPI.pas

Řětězce a Ansi vs. Unicode

Knihovna shlwapi obsahuje mnoho užitečných funkcí pro práci s řetězci. Viděli jsme, že většinu datových typů popsaných v dokumentaci můžeme přímo bez nejmenších potíží použít také v Delphi. S řetězci to ale tak jednoduché není. Řetězec v Delphi je vlastně ukazatel na dynamicky alokované pole znaků ukončené znakem s kódem 0. Ovšem před samotným polem znaků se nacházejí ještě dvě položky - jedna obsahuje délku řetězce a druhá počet odkazů na něj. Pokud mezi sebou přiřadíme dva řetězce, nedojde k vytvoření kopie pole znaků, ale pouze se o jedničku zvětší počítadlo odkazů. K vytvoření kopie dojde až v okamžiku změny jednoho z řetězců. Prázdné řetězce (řetězce nulové délky) mají hodnotu ukazatele nil. Všechny popsané operace se v Delphi dějí naprosto automaticky, bez zásahu programátora.
var  
  s, t: string;
...  
s := 'AHOJ';
t := s;
Naproti tomu, v jazyce C jsou řetězce ukazatele na pole znaků ukončené nulovým znakem, bez žádných dodatečných informací. Funkce Windows API používají řetězce jazyka C. V Delphi je pro ně rezervován typ PChar. Naštěstí, jak tedy vidíme, stačí při volání API funkcí, které mají řetězcové parametry, přetypovat řetězec string na ukazatel PChar, konstanty dokonce není nutno ani přetypovat. V případě různých výstupních bufferů je výhodnější než složitá manipulace s řetězci použít pole znaků indexované od nuly, se kterým lze zacházet podobně jako s řetězci. Aby vše nebylo tak jednoduché, není PChar jediný typ řetězců používaných ve Windows API. Každý prvek řetězce je reprezentován jedním bajtem, tzn. že obsahuje jeden z množiny 256 znaků. Pro abecedy, jako je např. japonská hiragana a katakana nebo arabská písma je 256 znaků nedostatečných. Proto bylo zavedeno kódování Unicode, kde je každý znak reprezentován dvěma bajty, tzn. je k dispozici přes 60 tisíc znaků. V Delphi byly pro podporu širokých znaků a řetězců zavedeny typy WideChar, WideString, pro komunikaci s Windows API slouží typ PWideChar. Kódování jednobajtových řetězců používaných ve Windows se nazývá ANSI (podle americké standardizační organizace). V Delphi jsou v současnosti typy AnsiChar a AnsiString shodné s typy Char a string. Oba typy řetězců můžeme v Delphi mezi sebou přiřazovat a přetypovávat, o potřebné konverze se stará kompilátor bez našeho vědomí. Stejně jako můžeme přetypovat string na PChar, můžeme také WideString přetypovat na PWideChar. Funkce s řetězcovými parametry jsou v knihovnách Windows přeloženy většinou ve dvou variantách: jméno ANSI varianty funkce končí písmenem A, Unicode verze má poslední písmeno W:
function PathIsRelativeA(lpszPath: PAnsiChar): BOOL; stdcall;
  external 'shlwapi.dll';

function PathIsRelativeW(lpszPath: PWideChar): BOOL; stdcall;
  external 'shlwapi.dll';
[Funkce vrací True pokud je cesta k souboru specifikovaná parametrem lpszPath relativní.]

Ve Windows 95/98/Me jsou domácím typem ANSI řetězce a naopak Windows NT/2000/XP používají Unicode řetězce. To ale neznamená, že ve Windows 9x/Me nemůžeme používat Unicode znaky a funkce a obráceně. Windows 9x/Me používají interně ANSI kódování a při volání Unicode funkce dojde ke konverzi parametru na ANSI řetězec a následně je vyvolána ANSI verze funkce. Ve Windows NT/2000/XP je tomu naopak.
Ne všechny funkce mají dvě varianty, např. už jednou zmíněná funkce pro extrakci ikon existuje ve třech formách: ExtractIconExA (ANSI), ExtractIconExW (Unicode) a ExtractIconEx (bez postfixu, také ANSI). To je spíše vyjímečný případ, funkce nově představené ve Windows XP najdeme v knihovnách už jen ve verzi s kódováním Unicode.
V tabulce jsou shrnuty řetězcové typy používané ve Windows API a jim odpovídající typy v Delphi:
LPSTR, LPCSTR PChar, PAnsiChar; LPSTR, LPCSTR (Windows.pas)
LPWSTR, LPWCSTR PWideChar, PWChar; LPWSTR, LPWCSTR (Windows.pas)
LPTSTR, LPCTSTR PChar i PWideChar
Právě v dokumentaci se setkáme nejčastěji s posledním typem LPTSTR, což znamená, že v knihovně existují obě verze funkce. V C je podle potřeby tento typ překládán jako ANSI nebo Unicode, stejně tak všechny funkce mají definovány svoje bezpostfixové jméno, které se podle nastaveného přepínače vyhodnotí jako ANSI nebo Unicode.
Bohužel v Delphi je tento typ vždy překládán jako ANSI, takže vytvořit plně Unicode aplikaci v Delphi je zatím takřka nemožné. Ve Windows.pas nalezneme tisíce podobných řádků:
function LoadLibraryA(lpLibFileName: PAnsiChar): HMODULE;
  stdcall; external 'kernel32.dll';

function LoadLibraryW(lpLibFileName: PWideChar): HMODULE;
  stdcall; external 'kernel32.dll';

function LoadLibrary(lpLibFileName: PAnsiChar): HMODULE;
  stdcall; external 'kernel32.dll' name 'LoadLibraryA';
Direktiva name říká: Budu používat funkci LoadLibrary, ale v knihovně kernel32.dll ji hledej pod jménem LoadLibraryA.

Funkce s proměnným počtem parametrů

Nakonec si všimněmě funkce wsprintf:
int wsprintf(LPTSTR lpOut, LPCTSTR lpFmt, ...);
Na místě výpustky lze při volání funkce dosadit žádnou, jednu nebo více hodnot různých datových typů. Funkce podle řetězce lpFmt a dodatečných parametrů naformátuje řetězec a uloží ho do bufferu lpOut. Nyní se podívejme na překvapení, jaké skrývá unit Windows.pas:
function wsprintf(Output: PChar; Format: PChar): Integer;
  stdcall; external 'user32.dll' name 'wsprintfA';
Takto deklarované funkci nemůžeme předat ani jeden dodatečný parametr. Dokonce ji ani nemůžeme degradovat na pouhé kopírování znaků mezi dvěma řetězci. Aplikace, která se pokusí použít tuto funkci s největší pravděpodobností dříve či později havaruje, protože u funkcí s různým počtem parametrů musíme použít volací konvenci jazyka C. V nových verzích Delphi existuje direktiva varargs právě pro import funkcí s různým počtem parametrů. Správná deklarace má tedy vypadat následovně:
function wsprintf(Output: PChar; Format: PChar): Integer; 
  cdecl; varargs; external 'user32.dll' name 'wsprintfA';
V dřívějších verzích Delphi, které neznají direktivu varargs, je možné vytvořit pro každý případ přetíženou deklaraci s potřebným počtem a typem parametrů. Naštěstí v knihovnách Delphi existuje v tomto případě funkce Format, která je v tomto prostředí mnohem lépe použitelná.

h2pas a jiné nástroje

Převádění hlavičkových souborů do paskalských unit je zdlouhavá a nudná operace, proto existuje několik více či méně zdařilých nástrojů pro automatizaci tohoto úkolu. Jeden z těch zdařilejších se nachází v archivech FreePascalu a jmenuje se symbolicky h2pas. Vstupem je hlavičkový soubor v jazyce C s deklaracemi funkcí a výstupem je unit se stejnými deklaracemi v Pascalu. Nakonec je stejně třeba výslednou unit zkontrolovat a případně poupravit.

Slovníček

  • Application Programming Interface, API - množina všech funkcí, poskytovaných operačním systémem programátorovi.
  • dynamicky linkovaná knihovna, DLL - funkce z této knihovny nejsou natrvalo (staticky) připojeny k programu, ale k propojení s aplikací dojde (dynamicky) až v okamžiku spuštení programu. Díky tomu, že DLL jsou přeloženy v samostatném souboru a sdíleny mezi několika aplikacemi, šetří se tak paměť i místo na disku.
  • hlavičkový soubor, header file, header - obdoba pascalské unit, soubor obsahující deklarace funkcí a tříd, definici typů a konstanty pro jazyk C/C++
  • volací konvence - způsob, jakým jsou funkci předávány parametry. Parametry se mohou předávat v registrech procesoru nebo přes zásobník. Důležité je, v jakém pořadí se parametry předávají (zleva nebo zprava) a kdo parametry odstraňuje ze zásobníku po skončení funkce (volající nebo volaná funkce).

Download

Stáhnout projekt pro Delphi 5 (19 KB)
Stáhnout ukázkovou aplikaci (191 KB)

Odkazy

Download Microsoft Platform SDK
Microsoft Developer Network
FreePascal
2003 – 2018 © Manison Softworks. Všechna práva vyhrazena.
Poslední aktualizace: 1. 12. 2003