A C# programozási nyelv

Marshalling

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;
}