Osvědčené postupy nativní interoperability
.NET nabízí různé způsoby přizpůsobení nativního kódu interoperability. Tento článek obsahuje pokyny, které týmy .NET společnosti Microsoft sledují pro nativní interoperabilitu.
Obecné pokyny
Pokyny v této části platí pro všechny scénáře spolupráce.
- ✔️ Pokud je to možné, použijte
[LibraryImport]
při cílení na .NET 7+.- Existují případy, kdy je použití
[DllImport]
vhodné. Analyzátor kódu s ID SYSLIB1054 vám řekne, kdy se jedná o tento případ.
- Existují případy, kdy je použití
- ✔️ Pro metody a parametry jako nativní metodu, kterou chcete volat, použijte stejné pojmenování a velká písmena.
- ✔️ ZVAŽTE použití stejného pojmenování a velká písmena pro konstantní hodnoty.
- ✔️ Použijte typy .NET, které se mapují nejblíže nativnímu typu. Například v jazyce C# použijte
uint
, pokud jeunsigned int
nativní typ . - ✔️ Dáváte přednost vyjádření nativních typů vyšší úrovně pomocí struktur .NET místo tříd.
- ✔️ Při předávání zpětných volání nespravovaným funkcím v jazyce C# raději použijte ukazatele
Delegate
funkcí. - ✔️ Do use
[In]
and[Out]
attributes on array parameters. - ✔️ Používejte
[In]
pouze atributy u[Out]
jiných typů, pokud se chování, které chcete, liší od výchozího chování. - ✔️ ZVAŽTE použití System.Buffers.ArrayPool<T> k sdružování nativních vyrovnávacích pamětí pole.
- ✔️ ZVAŽTE zabalení deklarací volání nespravovaného kódu do třídy se stejným názvem a velkými písmeny jako nativní knihovna.
- To umožňuje, aby funkce
[LibraryImport]
[DllImport]
jazyka C# používala funkci jazyka C#nameof
a předala název nativní knihovny a zajistili, že jste nezadali chybně napsaný název nativní knihovny.
- To umožňuje, aby funkce
- ✔️ Do use
SafeHandle
handles to manage lifetime of objects that encapsulate unmanaged resources. Další informace naleznete v tématu Čištění nespravovaných prostředků. - ❌ Vyhněte se finalizačním metodám pro správu životnosti objektů, které zapouzdřují nespravované prostředky. Další informace naleznete v tématu Implementace metody Dispose.
Nastavení atributu LibraryImport
Analyzátor kódu s ID SYSLIB1054 vám pomůže s LibraryImportAttribute
. Ve většině případů použití LibraryImportAttribute
vyžaduje explicitní deklaraci, nikoli spoléhání na výchozí nastavení. Tento návrh je záměrný a pomáhá vyhnout se nezamýšlenému chování ve scénářích spolupráce.
Nastavení atributu DllImport
Nastavení | Výchozí | Doporučení | Detaily |
---|---|---|---|
PreserveSig | true |
Zachovat výchozí | Pokud je tato hodnota explicitně nastavená na false, vrátí se neúspěšné návratové hodnoty HRESULT na výjimky (a vrácená hodnota v definici se v důsledku toho změní na hodnotu null). |
SetLastError | false |
Závisí na rozhraní API. | Tuto hodnotu nastavte na true, pokud rozhraní API používá GetLastError a k získání hodnoty použijte Marshal.GetLastWin32Error. Pokud rozhraní API nastaví podmínku s informací, že obsahuje chybu, zobrazí se chyba před provedením dalších volání, aby se zabránilo neúmyslnému přepsání. |
CharSet | Definovaný kompilátor (zadaný v dokumentaci ke znakové sadě) | Explicitní použití CharSet.Unicode nebo CharSet.Ansi použití řetězců nebo znaků v definici |
To určuje chování zařazování řetězců a co ExactSpelling dělá, když false . Všimněte si, že CharSet.Ansi ve skutečnosti je UTF8 v Unixu. Ve většině případů systém Windows používá Unicode, zatímco Unix používá UTF8. Další informace najdete v dokumentaci ke znakové sadě. |
ExactSpelling | false |
true |
Nastavte hodnotu true a získejte mírné výhody výkonu, protože modul runtime nebude hledat alternativní názvy funkcí s příponou "A" nebo "W" v závislosti na hodnotě CharSet nastavení ("A" pro CharSet.Ansi a "W" pro CharSet.Unicode ). |
Parametry řetězce
Připnutí string
a použití přímo nativním kódem (místo zkopírování) při předání hodnotou (ne ref
out
) a některou z následujících možností:
- LibraryImportAttribute.StringMarshalling je definován jako Utf16.
- Argument je explicitně označen jako
[MarshalAs(UnmanagedType.LPWSTR)]
. - DllImportAttribute.CharSet je Unicode.
❌ Nepoužívejte [Out] string
parametry. Řetězcové parametry předané hodnotou s [Out]
atributem můžou modul runtime synchronizovat, pokud je řetězec internedilovaným řetězcem. Další informace o prokládání řetězců naleznete v dokumentaci pro String.Intern.
✔️ ZVAŽTE char[]
nebo byte[]
pole z situace, kdy ArrayPool
se očekává, že nativní kód vyplní vyrovnávací paměť znaků. To vyžaduje předání argumentu jako [Out]
.
Pokyny specifické pro knihovnu DllImport
✔️ ZVAŽTE nastavení CharSet
vlastnosti tak [DllImport]
, aby modul runtime věděl očekávané kódování řetězců.
✔️ ZVAŽTE, jak se vyhnout parametrům StringBuilder
. StringBuilder
přiřazování vždy vytvoří nativní kopii vyrovnávací paměti. Proto může být velmi neefektivní. Typický scénář volání rozhraní API systému Windows, který přebírá řetězec:
StringBuilder
Vytvořte požadovanou kapacitu (přiděluje spravovanou kapacitu). {1}- Vyvolat:
- Přidělí nativní vyrovnávací paměť {2}.
- Zkopíruje obsah, pokud
[In]
(výchozí hodnota parametru)StringBuilder
- Zkopíruje nativní vyrovnávací paměť do nově přiděleného spravovaného pole, pokud
[Out]
{3} (také výchozí hodnota proStringBuilder
)).
ToString()
alokuje ještě další spravované pole {4}.
To je {4} přidělení pro získání řetězce z nativního kódu. Nejlepší, co můžete udělat, pokud chcete toto omezení omezit, je znovu použít StringBuilder
v jiném volání, ale tím se stále ukládá jenom jedno přidělení. Je mnohem lepší používat a ukládat do mezipaměti vyrovnávací paměť znaků z ArrayPool
. Pak se můžete dostat jenom na přidělení pro ToString()
následná volání.
Druhým problémem StringBuilder
je, že vždy kopíruje návratovou vyrovnávací paměť zpět na první hodnotu null. Pokud se předaný zpětný řetězec neukončí nebo se jedná o řetězec s dvojitým ukončením null, je nejlepší, aby byl váš P/Invoke nesprávný.
Pokud použijete StringBuilder
, jedna poslední gotcha je, že kapacita neobsahuje skrytou hodnotu null, která je vždy zohledněny v interoperabilitě. Je běžné, že lidé tuto chybu získají, protože většina rozhraní API chce velikost vyrovnávací paměti včetně hodnoty null. To může vést k plýtvání nebo zbytečným přidělením. Kromě toho tato chyba brání modulu runtime v optimalizaci StringBuilder
zařazování, aby se minimalizovaly kopie.
Další informace o zařazování řetězců naleznete v tématu Výchozí zařazování řetězců a přizpůsobení zařazování řetězců.
Windows Specific Pro
[Out]
řetězce CLR bude ve výchozím nastavení používatCoTaskMemFree
volné řetězce neboSysStringFree
řetězce, které jsou označeny jakoUnmanagedType.BSTR
. Pro většinu rozhraní API s vyrovnávací pamětí výstupního řetězce: Předaný počet znaků musí obsahovat hodnotu null. Pokud je vrácená hodnota menší než předaná hodnota počtu znaků, volání bylo úspěšné a hodnota je počet znaků bez koncové hodnoty null. V opačném případě je požadovaný počet vyrovnávací paměti včetně znaku null.
- Předat 5, získat 4: Řetězec je dlouhý 4 znaky s koncovou hodnotou null.
- Předat 5, získat 6: Řetězec je dlouhý 5 znaků, k uložení hodnoty null potřebujete 6 znakovou vyrovnávací paměť. Datové typy Windows pro řetězce
Logické parametry a pole
Logické hodnoty jsou snadné pokazit. Ve výchozím nastavení je .NET bool
zařazován do Windows BOOL
, kde se jedná o 4 bajtovou hodnotu. Nicméně _Bool
, a typy v C a bool
C++ jsou jeden bajt. To může vést k obtížnému sledování chyb, protože polovina návratové hodnoty bude zahozena, což může změnit pouze výsledek. Další informace o zařazování hodnot .NET bool
do typů C nebo C++ bool
najdete v dokumentaci k přizpůsobení logického zařazování polí.
Guid
Identifikátory GUID jsou použitelné přímo v podpisech. Mnoho rozhraní API systému Windows přebírají GUID&
aliasy typu, jako je REFIID
. Pokud podpis metody obsahuje referenční parametr, umístěte klíčové ref
slovo nebo [MarshalAs(UnmanagedType.LPStruct)]
atribut na deklaraci parametru GUID.
Identifikátor GUID | Identifikátor GUID podle odkazu |
---|---|
KNOWNFOLDERID |
REFKNOWNFOLDERID |
❌ Nepoužívejte [MarshalAs(UnmanagedType.LPStruct)]
nic jiného než ref
parametry GUID.
Blittable types
Typy Blittable jsou typy, které mají stejnou reprezentaci na úrovni bitu ve spravovaném a nativním kódu. Proto nemusí být převedeny do jiného formátu, aby byly zařazovány do a z nativního kódu, a vzhledem k tomu, že to zvyšuje výkon, měly by být upřednostňované. Některé typy nejsou blittable, ale jsou známé, že obsahují obsah blittable. Tyto typy mají podobné optimalizace jako blittable typy, pokud nejsou obsaženy v jiném typu, ale nejsou považovány za blittable, pokud jsou v polích struktur nebo pro účely UnmanagedCallersOnlyAttribute
.
Typy Blittable při povolení zařazování za běhu
Blittable types:
byte
,sbyte
, ,short
,int
ushort
,uint
long
ulong
, ,single
double
- struktury s pevným rozložením, které mají pouze typy blittable hodnot pro pole instancí
- Pevné rozložení vyžaduje
[StructLayout(LayoutKind.Sequential)]
nebo[StructLayout(LayoutKind.Explicit)]
- Struktury jsou
LayoutKind.Sequential
ve výchozím nastavení
- Pevné rozložení vyžaduje
Typy s obsahem s blittable:
- nenořené jednorozměrné pole primitivních typů s proměnlivou velikostí (například
int[]
) - třídy s pevným rozložením, které mají pouze blitelné typy hodnot pro pole instancí
- Pevné rozložení vyžaduje
[StructLayout(LayoutKind.Sequential)]
nebo[StructLayout(LayoutKind.Explicit)]
- třídy jsou
LayoutKind.Auto
ve výchozím nastavení
- Pevné rozložení vyžaduje
NOT blittable:
bool
NĚKDY blittable:
char
Typy s obsahem NĚKDY blittable:
string
Pokud jsou blittable typy předány odkazem s in
, ref
nebo out
, nebo pokud jsou typy s blittable obsahu předány podle hodnoty, jsou jednoduše připnuty marshallerem místo zkopírování do přechodné vyrovnávací paměti.
char
je blittable v jednorozměrném poli nebo je-li součástí typu, který obsahuje, je explicitně označen pomocí [StructLayout]
CharSet = CharSet.Unicode
.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeCharStruct
{
public char c;
}
string
obsahuje obsah blittable, pokud není obsažen v jiném typu a předává se hodnotou (ne ref
nebo out
) jako argument a některou z následujících možností:
- StringMarshalling je definován jako Utf16.
- Argument je explicitně označen jako
[MarshalAs(UnmanagedType.LPWSTR)]
. - CharSet je Unicode.
Můžete zjistit, zda je typ blittable nebo obsahuje blittable obsah pokusem o vytvoření připnuté GCHandle
. Pokud typ není řetězec nebo považován za blittable, GCHandle.Alloc
vyvolá chybu ArgumentException
.
Blittable types when runtime marshalling is disabled
Když je zařazování za běhu zakázané, pravidla, pro které typy jsou blittable, jsou výrazně jednodušší. Všechny typy, které jsou typy jazyka C# unmanaged
a nemají žádná pole označená [StructLayout(LayoutKind.Auto)]
, jsou blitelná. Všechny typy, které nejsou typy jazyka C# unmanaged
, nejsou blitelné. Koncept typů s blittable obsahem, jako jsou pole nebo řetězce, se nepoužije při zakázání zařazování za běhu. Jakýkoli typ, který není považován za blittable podle výše uvedeného pravidla, není podporován při zařazování za běhu je zakázáno.
Tato pravidla se liší od integrovaného systému především v situacích, kdy bool
se používají a char
používají. Při zařazování se bool
předává jako 1 bajtová hodnota a není normalizována a char
vždy se předává jako 2 bajtová hodnota. Pokud je povoleno zařazování za běhu, bool
může se mapovat na hodnotu 1, 2 nebo 4 bajtů a je vždy normalizována a char
v závislosti na hodnotě 1 nebo 2 bajtů se mapuje na CharSet
hodnotu 1 nebo 2 bajtů .
✔️ Pokud je to možné, udělejte strukturu blitelnou.
Další informace naleznete v tématu:
Udržování spravovaných objektů naživu
GC.KeepAlive()
zajistí, aby objekt zůstal v oboru, dokud se nestiskne metoda KeepAlive.
HandleRef
umožňuje marshalleru udržet objekt naživu po dobu trvání volání nespravovaného kódu. Dá se použít místo IntPtr
v podpisech metod. SafeHandle
nahrazuje tuto třídu a měla by být použita.
GCHandle
umožňuje připnout spravovaný objekt a získat na něj nativní ukazatel. Základní vzor je:
GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();
Připnutí není výchozí hodnota pro GCHandle
. Dalším hlavním vzorem je předání odkazu spravovanému objektu prostřednictvím nativního kódu a zpět do spravovaného kódu, obvykle s zpětným voláním. Tady je vzor:
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();
Nezapomeňte, že GCHandle
je potřeba explicitně uvolnit, aby nedošlo k nevracení paměti.
Běžné datové typy Windows
Tady je seznam datových typů, které se běžně používají v rozhraních API systému Windows a které typy jazyka C#, které se mají použít při volání do kódu Windows.
Následující typy mají stejnou velikost v 32bitovém a 64bitovém systému Windows bez ohledu na jejich názvy.
Šířka | Windows | C# | Alternativa |
---|---|---|---|
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 |
Viz CLong a CULong . |
32 | LONG32 |
int |
|
32 | CLONG |
uint |
Viz CLong a CULong . |
32 | DWORD |
uint |
Viz CLong a CULong . |
32 | DWORD32 |
uint |
|
32 | UINT |
uint |
|
32 | UINT32 |
uint |
|
32 | ULONG |
uint |
Viz CLong a 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 |
Následující typy, které jsou ukazateli, se řídí šířkou platformy. Používá se IntPtr
/UIntPtr
pro tyto účely.
Typy podepsaných ukazatelů (použití IntPtr ) |
Typy bez znaménka ukazatele (použití UIntPtr ) |
---|---|
HANDLE |
WPARAM |
HWND |
UINT_PTR |
HINSTANCE |
ULONG_PTR |
LPARAM |
SIZE_T |
LRESULT |
|
LONG_PTR |
|
INT_PTR |
Systém Windows PVOID
, který je C void*
, může být zařazován jako buď IntPtr
nebo UIntPtr
, ale preferovat void*
, pokud je to možné.
Dříve předdefinované podporované typy
Při odebrání integrované podpory typu existují vzácné instance.
Podpora UnmanagedType.HString
integrovaného UnmanagedType.IInspectable
zařazování byla odebrána ve verzi .NET 5. Binární soubory, které používají tento typ zařazování a které cílí na předchozí architekturu, je nutné překompilovat. Tento typ je stále možné zařažovat, ale musíte ho zařadovat ručně, jak ukazuje následující příklad kódu. Tento kód bude pokračovat a bude také kompatibilní s předchozími architekturami.
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);
Důležité informace o datových typech napříč platformami
Existují typy v jazyce C/C++, které mají zeměpisnou šířku v tom, jak jsou definovány. Při psaní vzájemné spolupráce mezi platformami můžou nastat případy, kdy se platformy liší a můžou způsobovat problémy, pokud se nepovažují.
C/C++ long
C/C++ long
a C# long
nemusí mít nutně stejnou velikost.
Typ long
v jazyce C/C++ je definován tak, aby měl alespoň 32 bitů. To znamená, že existuje minimální počet požadovaných bitů, ale v případě potřeby se platformy můžou rozhodnout použít více bitů. Následující tabulka ukazuje rozdíly v zadaných bitech datového typu C/C++ long
mezi platformami.
Platforma | 32bitová | 64bitová |
---|---|---|
Windows | 32 | 32 |
macOS/*nix | 32 | 64 |
Naproti tomu jazyk C# long
je vždy 64 bitů. Z tohoto důvodu je nejlepší se vyhnout použití jazyka C# long
ke spolupráci s C/C++ long
.
(Tento problém s C/C++ long
neexistuje pro C/C++ char
, short
a int
long long
protože jsou 8, 16, 32 a 64 bitů na všech těchto platformách.)
V .NET 6 a novějších verzích použijte CLong
a CULong
typy pro spolupráci s C/C++ long
a unsigned long
datovými typy. Následující příklad je pro CLong
, ale můžete použít CULong
k abstrakci unsigned long
podobným způsobem.
// Cross platform C function
// long Function(long a);
[DllImport("NativeLib")]
extern static CLong Function(CLong a);
// Usage
nint result = Function(new CLong(10)).Value;
Při cílení na .NET 5 a starší verze byste měli deklarovat samostatné podpisy systému Windows a jiné než Windows, které problém řeší.
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);
}
Struktury
Spravované struktury se vytvoří v zásobníku a neodeberou se, dokud metoda nevrátí. Podle definice se pak "připnou" (nepřesune se GC). Adresu můžete také jednoduše vzít v nebezpečných blocích kódu, pokud nativní kód nebude používat ukazatel za koncem aktuální metody.
Blittable struktury jsou mnohem výkonnější, protože je lze jednoduše použít přímo ve vrstvě zařazování. Pokuste se vytvořit strukturu blittable (například vyhnout bool
). Další informace najdete v části Typy tabulky Blittable .
Pokud je struktura blittable, použijte sizeof()
místo Marshal.SizeOf<MyStruct>()
pro lepší výkon. Jak je uvedeno výše, můžete ověřit, že typ je blitelný pokusem o vytvoření připnutého GCHandle
. Pokud typ není řetězec nebo považován za blittable, GCHandle.Alloc
vyvolá chybu ArgumentException
.
Ukazatele na struktury v definicích musí být předány ref
buď pomocí, nebo použití unsafe
a *
.
✔️ DO se co nejblíže shoduje se spravovanou strukturou s tvarem a názvy, které se používají v oficiální dokumentaci nebo hlavičce platformy.
✔️ K lepšímu výkonu používejte jazyk C# sizeof()
místo Marshal.SizeOf<MyStruct>()
pro blitelné struktury.
❌ Vyhněte se použití tříd k vyjádření složitých nativních typů prostřednictvím dědičnosti.
❌ Vyhněte se použití System.Delegate
polí nebo System.MulticastDelegate
polí k reprezentaci polí ukazatelů funkce ve strukturách.
Vzhledem k tomu System.Delegate , že a System.MulticastDelegate nemají požadovaný podpis, nezaručují, že delegát předaný bude odpovídat podpisu, který nativní kód očekává. Kromě toho může v rozhraní .NET Framework a .NET Core zařazování struktury obsahující System.Delegate
objekt nebo System.MulticastDelegate
z jeho nativní reprezentace do spravovaného objektu spustit modul runtime, pokud hodnota pole v nativní reprezentaci není ukazatel funkce, který zabalí spravovaný delegát. V .NET 5 a novějších verzích není podporováno zařazování System.Delegate
nebo System.MulticastDelegate
pole z nativní reprezentace do spravovaného objektu. System.Delegate
Místo nebo System.MulticastDelegate
.
Pevné vyrovnávací paměti
Pole, jako INT_PTR Reserved1[2]
je, musí být zařazováno na dvě IntPtr
pole, Reserved1a
a Reserved1b
. Pokud je nativní pole primitivním typem, můžeme klíčové slovo použít fixed
k jeho zápisu trochu čistěji. SYSTEM_PROCESS_INFORMATION
Například vypadá takto v nativní hlavičce:
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
...
} SYSTEM_PROCESS_INFORMATION
V jazyce C# ji můžeme napsat takto:
internal unsafe struct SYSTEM_PROCESS_INFORMATION
{
internal uint NextEntryOffset;
internal uint NumberOfThreads;
private fixed byte Reserved1[48];
internal Interop.UNICODE_STRING ImageName;
...
}
Existují však některé chytáky s pevnými vyrovnávacími paměťmi. Pevné vyrovnávací paměti neschytitelných typů nebudou správně zařazovány, takže je potřeba místní pole rozšířit na více jednotlivých polí. Kromě toho v rozhraní .NET Framework a .NET Core před 3.0 platí, že pokud je struktura obsahující pevné pole vyrovnávací paměti vnořená vnořená do nelimitovatelné struktury, pevné pole vyrovnávací paměti nebude správně zařazováno do nativního kódu.