Marshalling használata C# alatt.
Ez a leírás a menedzselt és nem menedzselt C# kódok közötti különbséget próbálja
röviden bemutatni. A leírás a 32 bites Windows operációs rendszerekre vonatkozik.
1. Mi a Marshalling?
A Marshalling egy olyan folyamat, amely hidat teremt a menedzselt kód és a nem menedzselt
kód között. Hordozza az üzeneteket a menedzselt környezetből a nem menedzselt környezetbe
és vissza. Ez az egyik alapvető szolgáltatása a CLR-nek. Mivel a nem menedzselt
környezetben lévő típusok közül soknak nincsen párja a menedzselt környezetben így
létre kell hozni konverziós rutinokat, amelyek átalakítják a kezelt típusokat kezeletlenre
és vissza. Ez a folyamat a Marshalling.
A .Net kódot menedzselt kódnak nevezzük, mert azt a CLR vezérli, menedzseli. Egyéb
kódokat, amelyeket nem a CLR vezérel, azt nem menedzselt kódoknak nevezzük.
2. Miért Marshalling?
A .Net nem tartalmazza pl. a következő típusokat:HRESULT, DWORD és HANDLE, amik
léteznek a nem menedzselt kód világában. Ezért meg kell találni, hogy a .Net-ben
mi helyettesíti őket, vagy sajátot kell csinálni, ha szükséges. Ezért kell a Marshalling.
Például a nem menedzselt DWORD egy előjel nélküli 32 bites integer így tudja a Marshalling
összekötni a .Net System.UInt32 típusával. Tehát a System.UInt32 helyettesíti a
nem menedzselt DWORD-öt. Az egyéb nem menedzselt összetett típusoknak (struktúrák,
unionok, stb.) nincs párjuk a menedzselt környezetben. Így létre kell hoznunk a
saját menedzselt típusainkat (struktúrák vagy osztályok), amelyek arra szolgálnak
majd, hogy menedzselt típusként helyettesítsék a nem menedzselteket.
3. Mikor van szükséges a Marshal-ra?
A Marshalling akkor jön jól, amikor nem menedzselt kódokkal dolgozunk például Windows
API-val vagy COM komponensekkel. Az alább ábra mutatja a Marsalling folyamatot,
hogy a két környezet között hogyan működik a kommunikációs folyamat.
Egyszerű és összetett adattípusok
Kétféle adattípus használatát mutatjuk be:
1. Egyszerű (primitív típusok)
2. Összetett típusok
Egyszerű adattípusok azok, amelyek nem tartalmaznak más adattípusokat. Ezek az alapjai
minden más típusnak. Példák a menedzselt primitív szám típusokra: System.Byte, System.Int32,
System.UInt32, ésSystem.Double.
Összetett adattípusok, amelyek más adattípusokból épülnek fel. Például egy osztály
vagy egy struktúra, amelyek magukban foglalnak egyszerű vagy egyéb összetett típusokat.
BLITTABLE ÉSNON-BLITTABLE adattípusok
A legtöbb adat típus egyaránt közösen kezeli a menedzselt és nem menedzselt memóriát,
és nem igényelnek speciális kezelést. Ezeket a típusokat hívják Blittable típusoknak.
Más típusok különleges kezelést igényelnek, ezeket hívjuk NON-BLITTABLE típusoknak.
A legtöbb egyszerű típus Blittable és minden összetett típus Non-Blittable. Az alábbi
táblázat a .Net-ben található Blittable adattípusok listáját tartalmazza.
Menedzselt típusok
|
Leírás
|
8-bit signed integer.
|
System.SByte
|
8-bit unsigned integer
|
System.Byte
|
16-bit signed integer.
|
System.Int16
|
16-bit unsigned integer
|
System.UInt16
|
32-bit signed integer
|
System.Int32
|
32-bit unsigned integer
|
System.UInt32
|
64-bit signed integer
|
System.Int64
|
64-bit unsigned integer
|
System.UInt64
|
Signed pointer
|
System.IntPtr
|
System.UIntPtr
|
Unsigned pointer
|
|
|
Szám adattípusok
A következő táblázat néhány nem menedzselt adattípust tartalmaz, amelyek Windows
alatt C/C++-ban léteznek, és megfelelteti mindegyikhez a hozzá tartozó .Net adattípust.
Leírás
|
Windows típus
|
C/C++ Kulcsszó
|
Menedzselt Típus
|
C# Kulcsszó
|
8-bit signed integer
|
CHAR
|
char
|
System.SByte
|
sbyte
|
8-bit unsigned integer
|
BYTE
|
unsignedchar
|
System.Byte
|
byte
|
16-bit signed integer
|
SHORT
|
Short
|
System.Int16
|
short
|
16-bit unsigned integer
|
WORD and USHORT
|
unsignedshort
|
System.UInt16
|
ushort
|
32-bit signed integer
|
INT, INT32, LONG, and LONG32
|
int, long
|
System.Int32
|
int
|
32-bit unsigned integer
|
DWORD, DWORD32, UINT, and UINT32
|
unsigned int, unsignedlong
|
System.UInt32
|
uint
|
64-bit signed integer
|
INT64, LONGLONG, and LONG64
|
__int64, longlong
|
System.Int64
|
long
|
64-bit unsigned integer
|
DWORDLONG, DWORD64, ULONGLONG, and UINT64
|
unsigned __int64, unsignedlonglong
|
System.UInt64
|
ulong
|
Floating-point integer
|
FLOAT
|
float
|
System.Double
|
double
|
Néhány nem menedzselt adattípusnak hasonló a neve, mint a menedzselt típusoknak,
de attól még azok jelentése különböző. Példa erre a LONG, aminek hasonló a neve,
mint a System.Long. Azonban a LONG 32 bites a System.Longpedig 64 bites!
Szöveges adattípusok
A szöveges nem menedzselt típusok Marshallingja bonyolultabb, mint a numerikusoké,
mert ezek a típusok non-blittable típusok, így különleges kezelést igényelnek. Az
alább táblázat röviden mutatja a nem menedzselt szöveges adattípusokat.
Leírás
|
Nem menedzselttípus(ok)
|
Menedzselt típus
|
8-bit ANSI character
|
CHAR
|
System.Char
|
16-bit Unicode character
|
WCHAR
|
System.Char
|
8-bit ANSI string of characters
|
LPSTR, LPCSTR, PCSTR, and PSTR
|
System.String
|
16-bit Unicode string of characters
|
LPCWSTR, LPWSTR, PCWSTR, and PWSTR
|
System.String
|
Most egy példa alapján létrehozzuk PInvoke segítségével a MessageBoxEx függvényt
C# alatt. A példa bemutatja, hogyan lehet a marshalling folyamattal ezt pontosan
megvalósítani. A MarshalAsAttribute attribútumot fogjuk használni. A példa eredményeként
egy üzenet fog megjelenni a felhasználó számára. A MessageBoxEx függvény eredeti
nem menedzselt definíciója:
intMessageBoxEx(
HWND hWnd,
LPCTSTR lpText,
LPCTSTR lpCaption,
UINT uType,
WORD wLanguageId );
Ugyanezt a függvényt C#-ban menedzselt kódra átírva PInvoke metódusnak nevezzük.
Az alább látható: (Az alkalmazás futtatásához C#-ban a System.Runtime.InteropServices
névteret usingolni kell. Ez a névtér minden egyes C#-osMarshallingos folyamathoz
feltétlenül szükséges.)
[DllImport("User32.dll", CharSet = CharSet.Unicode)]
[return: MarshalAs(UnmanagedType.I4)]
staticextern Int32 MessageBoxEx
(IntPtrhWnd,
// Unicode karakterek Marshallingja
[param: MarshalAs(UnmanagedType.LPTStr)]
StringlpText,
// Unicode karakterek Marshallingja
[param: MarshalAs(UnmanagedType.LPTStr)]
StringlpCaption,
// 4-byte-os (32-bites) unsigned integerMarshallingja
[param: MarshalAs(UnmanagedType.U4)]
UInt32 uType,
//2-byte-os (16-bites) unsigned integerMarshallingja
[param: MarshalAs(UnmanagedType.U2)]
UInt16 wLanguageId);
A Bool és a Boolean típus
Általában az egyszerű adattípusokat nagyon egyszerű Marshallingolni. Azonban a Boolean
egy non-blittable adattípus. Ezért ezeket külön kezelni kell. A Windows kétfajta
Boolean változó típust definiál.
1. Bool: Definíció szerint Int ezért 4 byte-os.
2. Boolean: Definíció szerint Byte, ezért csak 1 byte-os.
Mindkettőt be lehet állítani nullától különbözőre, ami igaz (TRUE) értéket jelent,
és nullára, ami hamis (False).
A Bool és Boolean típusokat a legjobb Marshallingolni a System.Boolean típusra,
de a Bool típust tudjuk Marshallingolni a System.Int32 típusra is, úgy mint egy
32 bites egészt. A Boolean típust is tudjuk MarshallingolniSystem.Byte vagy System.U1(definíció
szerint 8 bites integer) típusra.
A MarshalAsAttribute attribútumban kell beállítani, hogy a változónak mi az eredeti
nem menedzselt típusa. Ha az eredeti típus a Bool, akkor az UnmanagedType.Bool attribútum
beállítása az ajánlott, de meg lehet még adni a konstruktorban az UnmanagedType.I4-et
is. Ha elhagyjuk a MarshalAsAttribute attribútumot, akkor a CLR feltételezi, hogy
egy System.Boolean típusról van szó, ami 2 byte-os.
Példa: A híres CloseHandle függvény megvalósítása C#-ban.
Nem menedzselt kód: BOOL CloseHandle(HANDLE hObject);
A menedzselt C#-os verziója ennek a függvénynek:
[DllImport("Kernel32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
Ezen kívül lehetne a következő képen is Marshallingolni:
//[return: MarshalAs(UnmanagedType.I4)]
//Sőt meg lehet változtatni System.Boolean-ról System.Int32-re is!
staticexternBooleanCloseHandle(IntPtrhObject);
Összetett típusok Marshallingolása
Ebben a részben röviden bemutatom, hogyan kell az összetett típusokat Marshallingolni.
Az összetett típusok más típusokból épülnek fel. Ilyenek például az osztályok és
a struktúrák. A nem menedzselt összetett típusok két kategóriába tartoznak: a struktúrák
és az union-ok.
Ez a leírás a Struktúrák kezeléséről szól.
A nem menedzselt struktúrákat tudjuk Marshallingolni mint menedzselt struktúrákat,
vagy akár mint osztályokat. A választás a menedzselt struktúra és osztály között
csak a programozón áll, nincsenek szabályok, hogy mikor melyiket kellene használni.
Azonban ha menedzselt osztályként Marshallingoljuk a struktúrát, akkor vannak bizonyos
korlátok, amelyeket be kell tartanunk.
Amikor Marshallingolunk egy struktúrát a menedzselt környezetbe, akkor nagyon fontos,
hogy a struktúra mezőinek nevét és méretét a helyes sorrendben és teljesen pontos
mérettel adjuk meg.
A következő néhány lépés írja le, hogyan tudsz Marshallingolni egy nem menedzselt
struktúrát. 1. Hozz létre C#-ban Marshalling típusokkal a struktúrát vagy osztályt.
2.Add meg a típusokat a mezőkhöz.(Még egyszer a mezők sorrendje, mérete és típusa
rendkívül fontos, hogy a Windows rendesen elérje őket és használni tudja)
3. Egészítsd ki a típusokat StructLayoutAttribute attribútummal, ami meghatározza
a memória kiosztás típusát/fajtáját.
Amikor deklarálsz egy változót vagy egy típust a programban, akkor ez a memóriában
tárolódik és kap egy memória címet. Következésképpen minden adat tagnak a struktúra
belsejében saját címe van. Nézzük a következő szerkezetet:
A SMALL_RECT és COORD nem menedzselt struktúrák eredeti definíciója:
typedef struct SMALL_RECT {
SHORT Left;
SHORT Top;
SHORT Right;
SHORT Bottom;};
typedef struct COORD {
SHORT X;
SHORT Y;};
A memória elrendezési problémák kezelésére alkalmazzuk C#-ban a StructLayoutAttribute
attribútumot. Speciális típus Marshallingolásánál az elrendezési fajták kiválasztásra
szolgál a LayoutKind tulajdonság. Ez a tulajdonság egy értéket vehet fel a lehetséges
három közül:
1. LayoutKind.Auto (Alapértelmezett):A CLR határozza meg a típus alapján memória
elrendezést. Ha ezt az értéket válasszuk ki, akkor nem fogjuk tudni, hogy a nem
menedzselt típust mintájára tárolódott-e le pontosan a struktúra és kivétel keletkezhet
könnyedén.
2. LayoutKind.Sequential: (Általában ez az ajánlott) Ilyenkor a változók típusa
alapján sorrendben íródnak az értékek a memóriában. Ilyenkor valamennyi változó
a megfelelő sorrendben fog tárolódni a nem menedzselt struktúra definíció szerint.
3. LayoutKind.Explicit: Ha ezt választjuk a változók sorrendje ismét nagyon
fontos lesz. Ezt a típust akkor kell alkalmaznunk, ha a FieldOffsetAttribute attribútumot
is használjuk.(Általában Union esetén)
A SMALL_RECT és COORD menedzselt struktúrák C#-os definíciója (mind struktúraként
mind osztályként definiálva):
[StructLayout(LayoutKind.Sequential)]
//public class SMALL_RECT
public struct SMALL_RECT
{
public UInt16 Left;
public UInt16 Top;
public UInt16 Right;
public UInt16 Bottom;
}
[StructLayout(LayoutKind.Sequential)]
//public class COORD
public struct COORD
{
public UInt16 X;
public UInt16 Y;
}