Ajánlott natív együttműködési eljárások
A .NET különböző módszereket kínál a natív együttműködési kód testreszabására. Ez a cikk a Microsoft .NET-csapatai által a natív együttműködésre vonatkozó útmutatást tartalmazza.
Általános útmutatás
Az ebben a szakaszban található útmutató az összes interop-forgatókönyvre vonatkozik.
- ✔️ HA lehetséges, használja
[LibraryImport]
a .NET 7+-ot.- Vannak olyan esetek, amikor a használat
[DllImport]
megfelelő. A kódelemző SYSLIB1054 azonosítóval jelzi, hogy mikor van ez a helyzet.
- Vannak olyan esetek, amikor a használat
- ✔️ A DO ugyanazt az elnevezést és nagybetűsítést használja a metódusokhoz és paraméterekhez, mint a meghívni kívánt natív metódus.
- ✔️ FONTOLJA meg ugyanazt az elnevezést és nagybetűsítést az állandó értékekhez.
- ✔️ A DO a natív típushoz legközelebbi .NET-típusokat használja. A C#-ban például akkor érdemes használni
uint
, ha a natív típus.unsigned int
- ✔️ A DO inkább a magasabb szintű natív típusok kifejezését részesíti előnyben .NET-strukturálással, nem pedig osztályokkal.
- ✔️ A DO a típusokkal ellentétben
Delegate
inkább függvénymutatókat használ, amikor visszahívásokat ad át a C#-ban nem felügyelt függvényeknek. - ✔️ DO-használat
[In]
és[Out]
attribútumok tömbparamétereken. - ✔️ A DO csak akkor használja
[In]
és[Out]
attribútumokat más típusokon, ha a kívánt viselkedés eltér az alapértelmezett viselkedéstől. - ✔️ FONTOLJA meg a natív tömbpufferek készletének használatát System.Buffers.ArrayPool<T> .
- ✔️ FONTOLJA meg, hogy a P/Invoke deklarációkat egy olyan osztályba csomagolja, amelynek neve és nagybetűsítése megegyezik a natív kódtár nevével.
- Ez lehetővé teszi, hogy az ön
[LibraryImport]
vagy[DllImport]
attribútumok a C#nameof
nyelvi funkcióval adják át a natív kódtár nevét, és győződjön meg arról, hogy nem a natív kódtár nevét hibásan adta meg.
- Ez lehetővé teszi, hogy az ön
- ✔️ A DO leírókkal
SafeHandle
kezeli a nem felügyelt erőforrásokat tartalmazó objektumok élettartamát. További információ: Nem felügyelt erőforrások tisztítása. - ❌ KERÜLJE a véglegesítőket a nem felügyelt erőforrásokat tartalmazó objektumok élettartamának kezeléséhez. További információ: Implement a Dispose metódus.
LibraryImport attribútumbeállítások
A kódelemző SYSLIB1054 azonosítóval segíti a használatátLibraryImportAttribute
. A legtöbb esetben az alapértelmezett LibraryImportAttribute
beállítások használata helyett explicit deklarációra van szükség. Ez a kialakítás szándékos, és segít elkerülni a nem szándékos viselkedést az interop forgatókönyvekben.
DllImport attribútumbeállítások
Beállítás | Alapértelmezett | Ajánlás | Részletek |
---|---|---|---|
PreserveSig | true |
Alapértelmezett érték megtartva | Ha ez explicit módon hamis értékre van állítva, a sikertelen HRESULT visszatérési értékek kivételekké lesznek alakítva (és a definíció visszatérési értéke null értékű lesz. |
SetLastError | false |
Az API-tól függ | Állítsa ezt igazra, ha az API a GetLastErrort használja, és a Marshal.GetLastWin32Error használatával kéri le az értéket. Ha az API olyan feltételt állít be, amely azt jelzi, hogy hiba történt, kérje le a hibát, mielőtt más hívásokat indít, hogy véletlenül ne írja felül. |
CharSet | Fordító által definiált (a karakterkészlet dokumentációjában megadott) | Explicit módon használjon CharSet.Unicode sztringeket CharSet.Ansi vagy karaktereket a definícióban |
Ez határozza meg a sztringek rendezési viselkedését, és azt, hogy mikor mit ExactSpelling csinál false . Vegye figyelembe, hogy CharSet.Ansi valójában UTF8 a Unix.
A Windows legtöbbször Unicode-t, míg a Unix UTF8-at használ. További információt a charsets dokumentációjában talál. |
ExactSpelling | false |
true |
Állítsa ezt igaz értékre, és nyerjen némi előnyt, mivel a futtatókörnyezet nem keres alternatív függvényneveket az "A" vagy a "W" utótaggal a CharSet beállítás értékétől függően ("A" és CharSet.Ansi "W" for CharSet.Unicode ). |
Sztringparaméterek
Az A-t string
a natív kód rögzíti és használja közvetlenül (a másolás helyett), amikor az érték (nem ref
vagy out
) és az alábbiak bármelyike szerint van átadva:
- LibraryImportAttribute.StringMarshalling a következőként van definiálva: Utf16.
- Az argumentum explicit módon van megjelölve .
[MarshalAs(UnmanagedType.LPWSTR)]
- DllImportAttribute.CharSet az Unicode.
❌ NE használjon [Out] string
paramétereket. Az attribútummal [Out]
érték szerint átadott sztringparaméterek destabilizálhatják a futtatókörnyezetet, ha a sztring internált sztring. További információ a sztringek közötti internálásról a dokumentációban String.Intern.
✔️ FONTOLJA meg char[]
vagy byte[]
tömbök olyankor ArrayPool
, amikor a natív kód várhatóan kitölt egy karakterpuffert. Ehhez meg kell adni az argumentumot a következőként [Out]
: .
DllImport-specifikus útmutató
✔️ FONTOLJA meg a tulajdonság beállításátCharSet
, [DllImport]
hogy a futtatókörnyezet tudja a várt sztringkódolást.
✔️ FONTOLJA meg a paraméterek elkerülését StringBuilder
.
StringBuilder
a rendezés mindig létrehoz egy natív pufferpéldányt. Ezért rendkívül hatékony lehet. Egy sztringet használó Windows API meghívásának tipikus forgatókönyve:
- Hozzon létre egy
StringBuilder
kívánt kapacitást (lefoglalja a felügyelt kapacitást). {1} - Hív:
- Natív puffert {2}foglal le.
- Másolja a tartalmat, ha
[In]
(egy paraméter alapértelmezettStringBuilder
értéke). - Másolja a natív puffert egy újonnan lefoglalt felügyelt tömbbe, ha
[Out]
{3}StringBuilder
.
-
ToString()
egy újabb felügyelt tömböt {4}foglal le.
Ez a {4} kiosztások egy sztring natív kódból való kinyeréséhez. Ennek korlátozásához a legjobb, ha újra felhasználja a StringBuilder
másik hívásban, de ez továbbra is csak egy foglalást ment. Sokkal jobb a karakterpuffer használata és gyorsítótárazása.ArrayPool
Ezután egyszerűen lekérheti a későbbi hívások lefoglalását ToString()
.
A másik probléma StringBuilder
az, hogy a visszatérési puffert mindig az első null értékre másolja. Ha a visszaadott sztring nem szűnik meg, vagy dupla null értékű, akkor a P/Invoke függvény a legjobb esetben helytelen.
Ha mégis használjaStringBuilder
, az egyik utolsó gotcha az, hogy a kapacitás nemtartalmaz rejtett null értéket, amely mindig az interop függvényben van elszámolva. Gyakran előfordul, hogy a felhasználók tévednek, mivel a legtöbb API a puffer méretét szeretné, beleértve a null értéket is. Ez pazarlást/felesleges foglalásokat eredményezhet. Emellett ez a gotcha megakadályozza, hogy a futtatókörnyezet optimalizálja StringBuilder
a rendezést a másolatok minimalizálása érdekében.
A sztringek rendezéséről további információt a sztringek alapértelmezett rendezési és a sztringek testreszabása című témakörben talál.
A Windows-specifikus sztringek esetében
[Out]
a CLR alapértelmezés szerint a sztringek felszabadítására vagyCoTaskMemFree
aként megjelöltSysStringFree
sztringek használatára szolgálUnmanagedType.BSTR
. A kimeneti sztringpufferrel rendelkező API-k többségénél: Az átadott karakterszámnak tartalmaznia kell a null értéket. Ha a visszaadott érték kisebb, mint az átadott karakterszám, a hívás sikeres volt, és az érték a záró null érték nélküli karakterek száma. Ellenkező esetben a szám a puffer szükséges mérete, beleértve a null karaktert is.
- Pass in 5, get 4: A sztring 4 karakter hosszú egy záró null.
- Pass in 5, get 6: A sztring 5 karakter hosszú, 6 karakteres pufferre van szükség a null tárolásához. Windows-adattípusok sztringekhez
Logikai paraméterek és mezők
A logikai értékek könnyen elronthatóak. A .NET bool
alapértelmezés szerint windowsosra BOOL
van rendezve, ahol ez egy 4 bájtos érték. A C és _Bool
cbool
++ típusok azonban egyetlen bájtból állnak. Ez megnehezítheti a hibák nyomon követését, mivel a visszatérési érték fele el lesz vetve, ami csak potenciálisan módosítja az eredményt. A .NET-értékek bool
C vagy C++ bool
típusúra történő beállításával kapcsolatos további információkért tekintse meg a logikai mezők rendezési testreszabásának dokumentációját.
Guid
A GRAFIKUS GUID-k közvetlenül az aláírásokban használhatók. Sok Windows API olyan típusú aliasokat vesz fel GUID&
, mint a REFIID
. Ha a metódus aláírása hivatkozási paramétert tartalmaz, helyezzen el egy kulcsszót ref
vagy egy [MarshalAs(UnmanagedType.LPStruct)]
attribútumot a GUID paraméterdeklarációban.
GUID | By-ref GUID |
---|---|
KNOWNFOLDERID |
REFKNOWNFOLDERID |
❌ NE használja [MarshalAs(UnmanagedType.LPStruct)]
a GUID-paramétereken kívül ref
másra.
Blittable típusok
A Blittable-típusok olyan típusok, amelyek a felügyelt és natív kódban azonos bitszintű megjelenítéssel rendelkeznek. Ezért nem kell más formátumra konvertálni őket, hogy natív kódra és natív kódra legyenek rendezve, és mivel ez javítja a teljesítményt, előnyben kell részesíteni őket. Egyes típusok nem titkosak, de ismert, hogy titkos tartalmúak. Ezek a típusok hasonló optimalizálásokkal rendelkeznek, mint a titkos típusok, ha nem találhatók meg egy másik típusban, de nem tekinthetők titkosnak, ha a szerkezetek mezőiben UnmanagedCallersOnlyAttribute
vagy a .
Blittable típusok, ha a futtatókörnyezet-rendezés engedélyezve van
Titkos típusok:
-
byte
,sbyte
,short
,ushort
,int
uint
,long
,ulong
, ,single
double
- Rögzített elrendezésű szerkezetek, amelyek csak a példánymezőkhöz tartozó, nem módosítható értéktípusokkal rendelkeznek
- rögzített elrendezéshez vagy
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Explicit)]
- a szerkezetek
LayoutKind.Sequential
alapértelmezés szerint
- rögzített elrendezéshez vagy
Titkos tartalmú típusok:
- nem beágyazott, egydimenziós tömbök egydimenziós, kétliteres primitív típusokból (például
int[]
) - rögzített elrendezésű osztályok, amelyek csak a példánymezőkhöz tartozó írásvédett értéktípusokkal rendelkeznek
- rögzített elrendezéshez vagy
[StructLayout(LayoutKind.Sequential)]
[StructLayout(LayoutKind.Explicit)]
-
LayoutKind.Auto
az osztályok alapértelmezés szerint
- rögzített elrendezéshez vagy
NEM titkos:
bool
NÉHA blittable:
char
A SOMETIMES titkos tartalmú típusok:
string
Ha a blittable típusok hivatkozással in
vannak átadva a , ref
vagy out
, vagy, ha a blittable tartalmú típusok érték szerint vannak átadva, a rendező egyszerűen rögzíti őket ahelyett, hogy egy köztes pufferbe másolták őket.
char
egydimenziós tömbben található, vagy ha egy olyan típus része, amely tartalmazza, azzal kifejezetten megjelölve [StructLayout]
CharSet = CharSet.Unicode
van.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeCharStruct
{
public char c;
}
string
akkor tartalmazza a titkos kód tartalmát, ha az nem egy másik típusban található, és az érték (nem ref
vagy out
) argumentumként, valamint az alábbiak bármelyike szerint van átadva:
- StringMarshalling a következőként van definiálva: Utf16.
- Az argumentum explicit módon van megjelölve .
[MarshalAs(UnmanagedType.LPWSTR)]
- CharSet Unicode.
Egy rögzített típus létrehozásával GCHandle
megállapíthatja, hogy egy típus titkos vagy titkos tartalmú-e. Ha a típus nem sztring, vagy nem tekinthető titkos kódnak, GCHandle.Alloc
a rendszer egy ArgumentException
.
Titkos típusok, ha a futtatókörnyezet-rendezés le van tiltva
Ha a futtatókörnyezet-rendezés le van tiltva, jelentősen egyszerűbbek azok a szabályok, amelyek esetében a típusok ki vannak kapcsolva. Minden olyan típus, amely C# unmanaged
típusú, és nem rendelkezik olyan mezővel, amely jelöléssel [StructLayout(LayoutKind.Auto)]
rendelkezik, nem jelölhető. A nem C# unmanaged
típusú típusok nem teljesek. A titkos tartalmú típusok( például tömbök vagy sztringek) fogalma nem érvényes a futtatókörnyezet-rendezés letiltásakor. Ha a futásidejű rendezés le van tiltva, a fent említett szabály által nem minősített típus nem támogatott.
Ezek a szabályok eltérnek a beépített rendszertől, elsősorban olyan helyzetekben, ahol bool
és char
amelyeket használnak. Ha a rendezés le van tiltva, a rendszer 1 bájtos értékként adja át, bool
és nem normalizálja, és char
mindig 2 bájtos értékként adja át. Ha a futtatókörnyezet-rendezés engedélyezve van, 1, bool
2 vagy 4 bájtos értékre képezheti le a rendszer, és mindig normalizálható, és char
a függvénytől függően 1 vagy 2 bájtos értékre képezheti le a rendszer.CharSet
✔️ DO, hogy a struktúrákat, ha lehetséges.
További információk:
Felügyelt objektumok életben tartása
GC.KeepAlive()
biztosíthatja, hogy egy objektum a hatókörben maradjon, amíg el nem éri a KeepAlive metódust.
HandleRef
lehetővé teszi, hogy a rendező életben tartson egy objektumot a P/Invoke időtartamig. A metódus-aláírások helyett IntPtr
használható.
SafeHandle
hatékonyan helyettesíti ezt az osztályt, és inkább azt kell használni.
GCHandle
lehetővé teszi egy felügyelt objektum rögzítését és a natív mutató lekérését. Az alapminta a következő:
GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();
A rögzítés nem az alapértelmezett beállítás.GCHandle
A másik fő minta egy felügyelt objektumra mutató hivatkozás natív kódon keresztüli átadására és a felügyelt kódra való visszahívásra szolgál, általában visszahívással. A minta a következő:
GCHandle handle = GCHandle.Alloc(obj);
SomeNativeEnumerator(callbackDelegate, GCHandle.ToIntPtr(handle));
// In the callback
GCHandle handle = GCHandle.FromIntPtr(param);
object managedObject = handle.Target;
// After the last callback
handle.Free();
Ne felejtse el, hogy GCHandle
kifejezetten szabaddá kell tenni a memóriavesztés elkerülése érdekében.
Gyakori Windows-adattípusok
Az alábbiakban felsoroljuk a Windows API-kban gyakran használt adattípusokat, valamint a Windows-kódba való behíváskor használandó C#-típusokat.
A következő típusok mérete megegyezik a 32 bites és a 64 bites Windows esetében, a nevük ellenére.
Szélesség | Windows | C# | Alternatív megoldás |
---|---|---|---|
32 | BOOL |
int |
bool |
8 | BOOLEAN |
byte |
[MarshalAs(UnmanagedType.U1)] bool |
8 | BYTE |
byte |
|
8 | UCHAR |
byte |
|
8 | UINT8 |
byte |
|
8 | CCHAR |
byte |
|
8 | CHAR |
sbyte |
|
8 | CHAR |
sbyte |
|
8 | INT8 |
sbyte |
|
16 | CSHORT |
short |
|
16 | INT16 |
short |
|
16 | SHORT |
short |
|
16 | ATOM |
ushort |
|
16 | UINT16 |
ushort |
|
16 | USHORT |
ushort |
|
16 | WORD |
ushort |
|
32 | INT |
int |
|
32 | INT32 |
int |
|
32 | LONG |
int |
Lásd CLong és CULong . |
32 | LONG32 |
int |
|
32 | CLONG |
uint |
Lásd CLong és CULong . |
32 | DWORD |
uint |
Lásd CLong és CULong . |
32 | DWORD32 |
uint |
|
32 | UINT |
uint |
|
32 | UINT32 |
uint |
|
32 | ULONG |
uint |
Lásd CLong és CULong . |
32 | ULONG32 |
uint |
|
64 | INT64 |
long |
|
64 | LARGE_INTEGER |
long |
|
64 | LONG64 |
long |
|
64 | LONGLONG |
long |
|
64 | QWORD |
long |
|
64 | DWORD64 |
ulong |
|
64 | UINT64 |
ulong |
|
64 | ULONG64 |
ulong |
|
64 | ULONGLONG |
ulong |
|
64 | ULARGE_INTEGER |
ulong |
|
32 | HRESULT |
int |
|
32 | NTSTATUS |
int |
A következő típusok, mint a mutatók, a platform szélességét követik. Ezekhez használható IntPtr
/UIntPtr
.
Aláírt mutatótípusok (használat IntPtr ) |
Nem aláírt mutatótípusok (használat UIntPtr ) |
---|---|
HANDLE |
WPARAM |
HWND |
UINT_PTR |
HINSTANCE |
ULONG_PTR |
LPARAM |
SIZE_T |
LRESULT |
|
LONG_PTR |
|
INT_PTR |
A Windows PVOID
, amely egy C void*
, rendezhető, IntPtr
UIntPtr
vagy, de inkább void*
, ha lehetséges.
Korábban beépített támogatott típusok
Ritkán fordul elő, hogy egy típus beépített támogatása el lesz távolítva.
UnmanagedType.HString
A UnmanagedType.IInspectable
.NET 5 kiadásban eltávolítottuk a beépített marshal-támogatást. Újrafordítási bináris fájlokat kell használnia, amelyek ezt a rendezési típust használják, és amelyek egy korábbi keretrendszert céloznak meg. Ezt a típust továbbra is lehet végrehajtani, de manuálisan kell végrehajtania, ahogy az alábbi kódpéldában látható. Ez a kód tovább fog működni, és kompatibilis a korábbi keretrendszerekkel is.
public sealed class HStringMarshaler : ICustomMarshaler
{
public static readonly HStringMarshaler Instance = new HStringMarshaler();
public static ICustomMarshaler GetInstance(string _) => Instance;
public void CleanUpManagedData(object ManagedObj) { }
public void CleanUpNativeData(IntPtr pNativeData)
{
if (pNativeData != IntPtr.Zero)
{
Marshal.ThrowExceptionForHR(WindowsDeleteString(pNativeData));
}
}
public int GetNativeDataSize() => -1;
public IntPtr MarshalManagedToNative(object ManagedObj)
{
if (ManagedObj is null)
return IntPtr.Zero;
var str = (string)ManagedObj;
Marshal.ThrowExceptionForHR(WindowsCreateString(str, str.Length, out var ptr));
return ptr;
}
public object MarshalNativeToManaged(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return null;
var ptr = WindowsGetStringRawBuffer(pNativeData, out var length);
if (ptr == IntPtr.Zero)
return null;
if (length == 0)
return string.Empty;
return Marshal.PtrToStringUni(ptr, length);
}
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern int WindowsCreateString([MarshalAs(UnmanagedType.LPWStr)] string sourceString, int length, out IntPtr hstring);
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern int WindowsDeleteString(IntPtr hstring);
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern IntPtr WindowsGetStringRawBuffer(IntPtr hstring, out int length);
}
// Example usage:
[DllImport("api-ms-win-core-winrt-l1-1-0.dll", PreserveSig = true)]
internal static extern int RoGetActivationFactory(
/*[MarshalAs(UnmanagedType.HString)]*/[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(HStringMarshaler))] string activatableClassId,
[In] ref Guid iid,
[Out, MarshalAs(UnmanagedType.IUnknown)] out object factory);
Platformfüggetlen adattípussal kapcsolatos szempontok
A C/C++ nyelvben vannak olyan típusok, amelyek a definiálásuk során szélességi körekkel rendelkeznek. Platformfüggetlen interop írása esetén olyan esetek merülhetnek fel, amelyekben a platformok eltérnek, és ha nem veszik figyelembe, problémákat okozhatnak.
C/C++ long
A C/C++ long
és a C# long
nem feltétlenül azonos méretűek.
A long
C/C++ típus úgy van definiálva, hogy "legalább 32" bit legyen. Ez azt jelenti, hogy a szükséges bitek száma minimális, de a platformok dönthetnek úgy, hogy szükség esetén több bitet használnak. Az alábbi táblázat a C/C++ long
adattípushoz megadott bitek közötti különbségeket mutatja be a platformok között.
Platform | 32 bites | 64 bites |
---|---|---|
Windows | 32 | 32 |
macOS/*nix | 32 | 64 |
Ezzel szemben a C# long
mindig 64 bites. Ezért érdemes elkerülni a C# long
használatát a C/C++ long
billentyűkombinációval való együttműködéshez.
(Ez a C/C++ long
probléma nem létezik a C/C++ char
esetében, short
int
és long long
mivel ezek mindegyik platformon 8, 16, 32 és 64 bitesek.)
A .NET 6-os és újabb verzióiban a C/C++ CLong
és CULong
long
az adattípusok közötti együttműködéshez használja az unsigned long
és a típusokat. Az alábbi példa a következőkre mutatCLong
, de hasonló módon absztrakcióra CULong
is használhatóunsigned long
.
// Cross platform C function
// long Function(long a);
[DllImport("NativeLib")]
extern static CLong Function(CLong a);
// Usage
nint result = Function(new CLong(10)).Value;
A .NET 5-ös és korábbi verzióinak megcélzásakor külön Windows- és nem Windows-aláírásokat kell deklarálnia a probléma kezeléséhez.
static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// Cross platform C function
// long Function(long a);
[DllImport("NativeLib", EntryPoint = "Function")]
extern static int FunctionWindows(int a);
[DllImport("NativeLib", EntryPoint = "Function")]
extern static nint FunctionUnix(nint a);
// Usage
nint result;
if (IsWindows)
{
result = FunctionWindows(10);
}
else
{
result = FunctionUnix(10);
}
Struktúrák
A felügyelt szerkezetek a veremen jönnek létre, és a metódus visszatéréséig nem lesznek eltávolítva. Definíció szerint ezek "rögzítettek" (a GC nem fogja áthelyezni). Ha a natív kód nem használja a mutatót az aktuális metódus végén, egyszerűen felveheti a címet nem biztonságos kódblokkokban.
A strukturált szerkezetek sokkal hatékonyabbak, mivel egyszerűen közvetlenül használhatók a rendezési réteg által. Próbálja meg strukturáltsá tenni a szerkezeteket (például kerülje el bool
). További információkért lásd a Blittable Types szakaszt.
Ha a szerkezet nem megfelelő, használja sizeof()
a jobb teljesítmény helyett Marshal.SizeOf<MyStruct>()
. Ahogy fentebb említettük, a rögzített típus létrehozásával GCHandle
ellenőrizheti, hogy a típus nem látható-e. Ha a típus nem sztring, vagy nem tekinthető titkos kódnak, GCHandle.Alloc
a rendszer egy ArgumentException
.
A definíciók szerkezetére mutató mutatókat át kell adniref
, vagy használni és használni unsafe
*
kell.
✔️ A DO a lehető legszorosabban illeszkedik a felügyelt szerkezethez a hivatalos platform dokumentációjában vagy fejlécében használt alakzathoz és nevekhez.
✔️ NE használja a C#-ot sizeof()
Marshal.SizeOf<MyStruct>()
a nem strukturálható struktúrákhoz a teljesítmény javítása érdekében.
❌ NE függj a .NET futtatókörnyezeti kódtárak által közzétett strukturált típusok belső ábrázolásától, kivéve, ha az kifejezetten dokumentálva van.
❌ NE használjon osztályokat összetett natív típusok öröklés útján történő kifejezéséhez.
❌ KERÜLJE, hogy függvénymutató-mezőket System.Delegate
System.MulticastDelegate
jelöljön a struktúrákban.
System.Delegate Mivel System.MulticastDelegate nem rendelkezik szükséges aláírással, nem garantálják, hogy az átadott meghatalmazott megegyezik a natív kód által elvárt aláírással. Emellett a .NET-keretrendszer és a .NET Core esetén a felügyelt objektumok natív reprezentációját System.Delegate
tartalmazó vagy System.MulticastDelegate
azokból származó szerkezetek beállítása destabilizálhatja a futtatókörnyezetet, ha a natív ábrázolásban szereplő mező értéke nem egy felügyelt delegáltat burkoló függvénymutató. A .NET 5-ös és újabb verzióiban egy vagy System.Delegate
több mező natív ábrázolásból felügyelt objektumba történő rendezése System.MulticastDelegate
nem támogatott. Használjon egy adott delegálttípust ahelyett System.Delegate
vagy System.MulticastDelegate
.
Rögzített pufferek
Egy olyan tömböt, mint INT_PTR Reserved1[2]
két mezőre kell rendeződni IntPtr
, Reserved1a
és Reserved1b
. Ha a natív tömb egy primitív típus, a fixed
kulcsszóval egy kicsit tisztábban írhatjuk. Például így SYSTEM_PROCESS_INFORMATION
néz ki a natív fejlécben:
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
...
} SYSTEM_PROCESS_INFORMATION
A C#-ban így írhatjuk:
internal unsafe struct SYSTEM_PROCESS_INFORMATION
{
internal uint NextEntryOffset;
internal uint NumberOfThreads;
private fixed byte Reserved1[48];
internal Interop.UNICODE_STRING ImageName;
...
}
Vannak azonban rögzített pufferekkel rendelkező gotcha-k. A nem titkos típusok rögzített pufferei nem lesznek megfelelően rendezve, ezért a helyben lévő tömböt több egyéni mezőre kell kiterjeszteni. Emellett a 3.0 előtti .NET-keretrendszer és a .NET Core esetén, ha egy rögzített puffermezőt tartalmazó struktúra egy nem titkos szerkezetbe van ágyazva, a rögzített puffermező nem lesz megfelelően rendezve a natív kódhoz.