pondělí 12. září 2011

Mixed DLL - Kombinované DLL - (.net a nativní)

Před 14 dny jsem dostal úkol, volat funkce a DLL mého kolegy. DLL je napsané v C++ Builderu tedy v nativním běhovém prostředí. Spouštění funkcí z nativního DLL není nic světoborného a .NET na to má sekci zvanou System.Runtime.InteropServices. Rozhodl jsem se napsat tuto glosu, protože občas se mi DLL zavolá v pořádku a občas volání selže. Hledal jsem nejrůznější články na googlu a narazil jsem pouze na tzv. Mixed DLL problém:
http://www.codeguru.com/columns/kate/article.php/c3643

Na MSDN je celý článek o volání native DLL:
http://msdn.microsoft.com/en-us/magazine/cc164123.aspx

Důležitým parametrem pro volání je volací konvence CallingConvention = CallingConvention.Cdecl. Máte na výběr z několika volacích konvencí: Cdecl, StdCall, FastCall, ThisCall a WinApi

Při volání funkcí předáváme parametry a musíme zajistit, aby parametry z .net odpovídali parametrům z nativního světa.

char = System.SByte
int = System.Int32

O předávání dat se stará tzv. Marshaling
Můžeme využít defaultního předávání dat, ale pokud z nějakého důvodu .net typ neodpovídá nativnímu typu mužeme si explicitně říct, jakým způsobem chceme data u konkrétního parametru předat. K tomu slouží: [MarshalAs(UnmanagedType.LPWStr)]jméno_parametru. Toto využijeme zvláště při například u stringů nebo složitějších polí.

Pokud v .net pracujeme se stringem, definujeme jej jako string. V C/C++ je situace poněkud složitější. Můžeme pracovat s C stringem, to je pole znaků char, pak můžeme mít pole unicode znaků wchar_t nebo bstr stringy a podobně. Proto při předávání .NETu musíme explicitně říct, jaký string chceme do nativního prostředí předat.

Příklad: BStr, LPStr, LPWStr,  LPTStr atd.

Příklad volání:
[DllImport("ImportGS.dll", EntryPoint = "_DB_ImportXML", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
public static extern int db_ImportXML(string filename, TFileType type, bool full);

Importovanou funkci si můžete pojmenovat libovolným způsobem, ale pak musíte její jméno napsat do Attributu EntryPoint a také musíte dát pozor na volací konvence a '_'. Dáe musíte dát pozor na typy řetězců.

Nyní již umíme zavolat nativní dll, ale podívejme se ješte jak může takové dll zavolat náš .net kód zpět pomocí callbacku.

Callback
V hlavičkovém souboru jsem měl definovaný callback:
typedef void (TNotice)(wchar_t *msg, bool error);
V Céčku je callback reprezentovaný jako ukazatel nafunkci. V .NETu takový callback realizujeme pomocí delegáta:

 [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
 public delegate void CallbackDelegate([MarshalAs(UnmanagedType.LPWStr)]StringBuilder msg, bool error);

Explicitně říkám, že jde o UnmanagedFunctionPointer (ukazatel z jiného světa :)) a určím jeho volací konvenci. Pak definuju delegáta tak, aby mu odpovídali parametry s jeho Céčkovským protějškem. A také určím, jak se mají předávat parametry Marshalingem.

Na závěr bych chtěl ještě upozornit na možnost předávat a zpracovávat parametry více způsoby. Vezměme si oblíbený string.

String je možné předávat jako string, StringBuilder a nebo například jako IntPtr, který si přetypujeme až v těle delegáta.

StringBuilder využíváme v případě, že se řetězec předává jako reference a vrátí se mi zpět změněný.

Pokud si předáme string jako IntPtr msg, tak výsledný string získáme voláním:
string msg_decode = System.Runtime.InteropServices.Marshal.PtrToStringUni(msg);


Žádné komentáře: