Aanbevolen procedures voor systeemeigen interoperabiliteit
.NET biedt u verschillende manieren om uw systeemeigen interoperabiliteitscode aan te passen. Dit artikel bevat de richtlijnen die microsoft's .NET-teams volgen voor systeemeigen interoperabiliteit.
Algemene richtlijnen
De richtlijnen in deze sectie zijn van toepassing op alle interoperabiliteitsscenario's.
- ✔️
[LibraryImport]
GEBRUIK, indien mogelijk, bij het richten van .NET 7+. - ✔️ GEBRUIK dezelfde naamgeving en hoofdlettergebruik voor uw methoden en parameters als de systeemeigen methode die u wilt aanroepen.
- ✔️ OVERWEEG dezelfde naamgeving en hetzelfde hoofdlettergebruik te gebruiken voor constante waarden.
- ✔️ Gebruik .NET-typen die het dichtst bij het systeemeigen type zijn toegewezen. Gebruik bijvoorbeeld in C#
uint
wanneer het systeemeigen type isunsigned int
. - ✔️ Geef de voorkeur aan het uitdrukken van systeemeigen typen op een hoger niveau met behulp van .NET-structs in plaats van klassen.
- ✔️ Gebruik liever functieaanwijzers in plaats van
Delegate
typen wanneer callbacks worden doorgegeven aan niet-beheerde functies in C#. - ✔️ DO use
[In]
and[Out]
attributes on array parameters. - ✔️ GEBRUIK en
[Out]
kenmerken voor andere typen alleen[In]
als het gewenste gedrag verschilt van het standaardgedrag. - ✔️ OVERWEEG om System.Buffers.ArrayPool<T> uw systeemeigen matrixbuffers te groeperen.
- ✔️ OVERWEEG om uw P/Invoke-declaraties in een klasse te verpakken met dezelfde naam en hetzelfde hoofdlettergebruik als uw systeemeigen bibliotheek.
- Hierdoor kunnen uw
[LibraryImport]
of[DllImport]
kenmerken de C#nameof
-taalfunctie gebruiken om de naam van de systeemeigen bibliotheek door te geven en ervoor te zorgen dat u de naam van de systeemeigen bibliotheek niet verkeerd hebt gespeld.
- Hierdoor kunnen uw
- ✔️ Do use
SafeHandle
handles to manage lifetime of objects that encapsulate unmanaged resources. Zie Onbeheerde resources opschonen voor meer informatie. - ❌ VERMIJD finalizers voor het beheren van de levensduur van objecten die niet-beheerde resources inkapselen. Zie Een verwijderingsmethode implementeren voor meer informatie.
Kenmerkinstellingen LibraryImport
Een codeanalyse, met id-SYSLIB1054, helpt u bij het gebruik van LibraryImportAttribute
. In de meeste gevallen vereist het gebruik van LibraryImportAttribute
een expliciete declaratie in plaats van te vertrouwen op standaardinstellingen. Dit ontwerp is opzettelijk en helpt onbedoeld gedrag te voorkomen in interop-scenario's.
Kenmerkinstellingen dllImport
Instelling | Standaardinstelling | Aanbeveling | DETAILS |
---|---|---|---|
PreserveSig | true |
Standaard behouden | Wanneer dit expliciet is ingesteld op onwaar, worden mislukte HRESULT-retourwaarden omgezet in uitzonderingen (en wordt de retourwaarde in de definitie null als gevolg hiervan). |
SetLastError | false |
Afhankelijk van de API | Stel dit in op true als de API GetLastError gebruikt en Marshal.GetLastWin32Error gebruikt om de waarde op te halen. Als de API een voorwaarde instelt die aangeeft dat deze een fout bevat, krijgt u de fout voordat u andere aanroepen uitvoert om onbedoeld te voorkomen dat deze wordt overschreven. |
CharSet | Compilergedefinieerd (opgegeven in de charset-documentatie) | Expliciet gebruiken CharSet.Unicode of CharSet.Ansi wanneer tekenreeksen of tekens aanwezig zijn in de definitie |
Hiermee geeft u marshallgedrag van tekenreeksen en wat ExactSpelling doet wanneer false . Let op: CharSet.Ansi UTF8 op Unix. In de meeste tijd gebruikt Windows Unicode terwijl Unix UTF8 gebruikt. Zie meer informatie over de documentatie over tekenssets. |
ExactSpelling | false |
true |
Stel dit in op waar en krijg een klein voordeel omdat de runtime niet zoekt naar alternatieve functienamen met een achtervoegsel 'A' of 'W', afhankelijk van de waarde van de CharSet instelling ('A' voor CharSet.Ansi en 'W' voor CharSet.Unicode ). |
Tekenreeksparameters
Een string
wordt vastgemaakt en rechtstreeks gebruikt door systeemeigen code (in plaats van gekopieerd) wanneer deze wordt doorgegeven door waarde (niet ref
of out
) en een van de volgende opties:
- LibraryImportAttribute.StringMarshalling is gedefinieerd als Utf16.
- Het argument wordt expliciet gemarkeerd als
[MarshalAs(UnmanagedType.LPWSTR)]
. - DllImportAttribute.CharSet is Unicode.
❌ GEBRUIK GEEN [Out] string
parameters. Tekenreeksparameters die door de waarde worden doorgegeven met het [Out]
kenmerk, kunnen de runtime stabiliseren als de tekenreeks een interne tekenreeks is. Zie meer informatie over het interneren van tekenreeksen in de documentatie voor String.Intern.
✔️ HOUD REKENING char[]
of byte[]
matrices van een ArrayPool
wanneer systeemeigen code naar verwachting een tekenbuffer vult. Hiervoor moet het argument worden doorgegeven als [Out]
.
DllImport-specifieke richtlijnen
✔️ OVERWEEG de CharSet
eigenschap in [DllImport]
te stellen zodat de runtime de verwachte tekenreekscodering kent.
✔️ OVERWEEG parameters te StringBuilder
vermijden. StringBuilder
marshalling maakt altijd een systeemeigen bufferkopie. Als zodanig kan het uiterst inefficiënt zijn. Neem het gebruikelijke scenario voor het aanroepen van een Windows-API die een tekenreeks gebruikt:
- Maak een
StringBuilder
van de gewenste capaciteit (wijst beheerde capaciteit toe) {1}. - Aanroepen:
- Wijst een systeemeigen buffer {2}toe.
- Hiermee kopieert u de inhoud als
[In]
(de standaardwaarde voor eenStringBuilder
parameter). - Kopieert de systeemeigen buffer naar een zojuist toegewezen beheerde matrix als
[Out]
{3} (ook de standaardwaarde voorStringBuilder
).
ToString()
wijst nog een beheerde matrix {4}toe.
Dat zijn {4} toewijzingen om een tekenreeks uit systeemeigen code te halen. Het beste dat u kunt doen om dit te beperken, is het StringBuilder
opnieuw gebruiken in een andere aanroep, maar dit slaat nog steeds slechts één toewijzing op. Het is veel beter om een tekenbuffer van te gebruiken en in de cache op te slaan.ArrayPool
Vervolgens kunt u alleen de toewijzing voor de ToString()
volgende aanroepen krijgen.
Het andere probleem is StringBuilder
dat de retourbuffer altijd wordt gekopieerd naar de eerste null. Als de doorgegeven tekenreeks niet is beëindigd of een dubbel null-beëindigde tekenreeks is, is uw P/Invoke het beste onjuist.
Als u wel gebruiktStringBuilder
, is een laatste gotcha dat de capaciteit geen verborgen null bevat, die altijd in interop wordt opgenomen. Het is gebruikelijk dat mensen dit fout krijgen, omdat de meeste API's de grootte van de buffer willen, inclusief de null. Dit kan leiden tot verspilde/onnodige toewijzingen. Bovendien voorkomt deze gotcha dat de runtime marshalling optimaliseert StringBuilder
om kopieën te minimaliseren.
Zie Default Marshalling for Strings and Customizing string marshalling (Standaard marshalling voor tekenreeksen aanpassen) voor meer informatie over tekenreeksen marshalling.
Windows Specific For
[Out]
strings the CLR will defaultCoTaskMemFree
to free strings orSysStringFree
for strings that are marked asUnmanagedType.BSTR
. Voor de meeste API's met een uitvoertekenreeksbuffer: het aantal doorgegeven tekens moet de null bevatten. Als de geretourneerde waarde kleiner is dan het aantal doorgegeven tekens, is de aanroep geslaagd en is de waarde het aantal tekens zonder de afsluitende null. Anders is het aantal de vereiste grootte van de buffer , inclusief het null-teken.
- Geef 5 door, haal 4 op: De tekenreeks is 4 tekens lang met een afsluitende null.
- Geef 5 door, haal 6 op: De tekenreeks is vijf tekens lang, moet een buffer van 6 tekens bevatten om de null-waarde op te slaan. Windows-gegevenstypen voor tekenreeksen
Booleaanse parameters en velden
Booleaanse waarden zijn gemakkelijk te verknoeien. Standaard wordt een .NET bool
marshalled naar een Windows BOOL
, waarbij het een 4-bytewaarde is. _Bool
De en bool
typen in C en C++ zijn echter één byte. Dit kan ertoe leiden dat fouten moeilijk worden opgespoord omdat de helft van de retourwaarde wordt verwijderd, waardoor het resultaat alleen mogelijk wordt gewijzigd. Zie de documentatie over het aanpassen van booleaanse veld marshalling voor meer informatie over het marshallen van .NET-waarden bool
naar C- of C++bool
-typen.
GUID's
GUID's kunnen rechtstreeks in handtekeningen worden gebruikt. Veel Windows-API's maken GUID&
typealiassen zoals REFIID
. Wanneer de methodehandtekening een verwijzingsparameter bevat, plaatst u een ref
trefwoord of een [MarshalAs(UnmanagedType.LPStruct)]
kenmerk in de declaratie van de GUID-parameter.
GUID | GUID voor verw |
---|---|
KNOWNFOLDERID |
REFKNOWNFOLDERID |
❌ Niet gebruiken [MarshalAs(UnmanagedType.LPStruct)]
voor iets anders dan ref
GUID-parameters.
Blittable-typen
Blittable-typen zijn typen met dezelfde weergave op bitniveau in beheerde en systeemeigen code. Als zodanig hoeven ze niet te worden geconverteerd naar een andere indeling om te worden ge marshalld naar en van systeemeigen code, en omdat dit de prestaties verbetert, moeten ze de voorkeur hebben. Sommige typen zijn niet belicht, maar zijn bekend dat ze blittable-inhoud bevatten. Deze typen hebben vergelijkbare optimalisaties als belichte typen wanneer ze niet in een ander type zijn opgenomen, maar worden niet beschouwd als belichtbaar in velden van structs of voor de doeleinden van UnmanagedCallersOnlyAttribute
.
Blittable-typen wanneer runtime-marshalling is ingeschakeld
Blittable-typen:
byte
, ,short
,ushort
sbyte
, ,int
,uint
, ,single
ulong
long
double
- structs met een vaste indeling die alleen belichte waardetypen voor exemplaarvelden hebben
- voor een vaste indeling is
[StructLayout(LayoutKind.Sequential)]
vereist of[StructLayout(LayoutKind.Explicit)]
- structs zijn
LayoutKind.Sequential
standaard
- voor een vaste indeling is
Typen met blittable-inhoud:
- niet-geneste, eendimensionale matrices van blittable primitieve typen (bijvoorbeeld
int[]
) - klassen met een vaste indeling met alleen belichte waardetypen voor exemplaarvelden
- voor een vaste indeling is
[StructLayout(LayoutKind.Sequential)]
vereist of[StructLayout(LayoutKind.Explicit)]
- klassen zijn
LayoutKind.Auto
standaard
- voor een vaste indeling is
NIET afgesplitsbaar:
bool
SOMS blittable:
char
Typen met soms belichte inhoud:
string
Wanneer blittable-typen worden doorgegeven door verwijzing met in
, ref
of, of out
wanneer typen met afgelichte inhoud worden doorgegeven door een waarde, worden ze gewoon vastgemaakt door de marshaller in plaats van te worden gekopieerd naar een tussenliggende buffer.
char
is belicht in een eendimensionale matrix of als deze deel uitmaakt van een type dat het bevat, expliciet is gemarkeerd met [StructLayout]
CharSet = CharSet.Unicode
.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeCharStruct
{
public char c;
}
string
bevat blittable-inhoud als deze niet is opgenomen in een ander type en wordt doorgegeven door een waarde (niet ref
of out
) als argument en een van de volgende opties:
- StringMarshalling is gedefinieerd als Utf16.
- Het argument wordt expliciet gemarkeerd als
[MarshalAs(UnmanagedType.LPWSTR)]
. - CharSet is Unicode.
U kunt zien of een type blittable is of blittable-inhoud bevat door een vastgemaakte GCHandle
inhoud te maken. Als het type geen tekenreeks is of als blittable wordt beschouwd, GCHandle.Alloc
genereert u een ArgumentException
.
Blittable-typen wanneer runtime-marshalling is uitgeschakeld
Wanneer runtime-marshalling is uitgeschakeld, zijn de regels waarvoor typen worden belicht aanzienlijk eenvoudiger. Alle typen die C#unmanaged
-typen zijn en die geen velden bevatten die zijn gemarkeerd, [StructLayout(LayoutKind.Auto)]
kunnen worden afgelicht. Alle typen die geen C# unmanaged
-typen zijn, zijn niet belicht. Het concept van typen met belichte inhoud, zoals matrices of tekenreeksen, is niet van toepassing wanneer runtime-marshalling is uitgeschakeld. Elk type dat door de bovengenoemde regel niet als belicht wordt beschouwd, wordt niet ondersteund wanneer runtime-marshalling is uitgeschakeld.
Deze regels verschillen voornamelijk van het ingebouwde systeem in situaties waarin bool
en char
worden gebruikt. Wanneer marshalling is uitgeschakeld, bool
wordt deze doorgegeven als een 1-bytewaarde en niet genormaliseerd en char
wordt altijd doorgegeven als een 2-bytewaarde. Wanneer runtime-marshalling is ingeschakeld, bool
kan deze worden toegewezen aan een waarde van 1, 2 of 4 bytes en wordt deze altijd genormaliseerd en char
toegewezen aan een waarde van 1 of 2 bytes, afhankelijk van de CharSet
.
✔️ MAAK uw structuren indien mogelijk blittable.
Zie voor meer informatie:
Beheerde objecten actief houden
GC.KeepAlive()
zorgt ervoor dat een object binnen het bereik blijft totdat de KeepAlive-methode wordt bereikt.
HandleRef
stelt de marshaller in staat om een object actief te houden voor de duur van een P/Invoke. Deze kan worden gebruikt in plaats van IntPtr
in methodehandtekeningen. SafeHandle
vervangt deze klasse effectief en moet in plaats daarvan worden gebruikt.
GCHandle
staat het vastmaken van een beheerd object toe en het ophalen van de systeemeigen aanwijzer. Het basispatroon is:
GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();
Vastmaken is niet de standaardinstelling voor GCHandle
. Het andere belangrijke patroon is het doorgeven van een verwijzing naar een beheerd object via systeemeigen code en terug naar beheerde code, meestal met een callback. Dit is het patroon:
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();
Vergeet niet dat dit GCHandle
expliciet moet worden vrijgemaakt om geheugenlekken te voorkomen.
Algemene Windows-gegevenstypen
Hier volgt een lijst met gegevenstypen die vaak worden gebruikt in Windows-API's en welke C#-typen moeten worden gebruikt bij het aanroepen van de Windows-code.
De volgende typen hebben dezelfde grootte op 32-bits en 64-bits Windows, ondanks hun namen.
Width | Windows | C# | Alternatief |
---|---|---|---|
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 |
Zie CLong en CULong . |
32 | LONG32 |
int |
|
32 | CLONG |
uint |
Zie CLong en CULong . |
32 | DWORD |
uint |
Zie CLong en CULong . |
32 | DWORD32 |
uint |
|
32 | UINT |
uint |
|
32 | UINT32 |
uint |
|
32 | ULONG |
uint |
Zie CLong en 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 |
De volgende typen, aanwijzers, volgen de breedte van het platform. Gebruik IntPtr
/UIntPtr
hiervoor.
Ondertekende aanwijzertypen (gebruik IntPtr ) |
Niet-ondertekende aanwijzertypen (gebruik UIntPtr ) |
---|---|
HANDLE |
WPARAM |
HWND |
UINT_PTR |
HINSTANCE |
ULONG_PTR |
LPARAM |
SIZE_T |
LRESULT |
|
LONG_PTR |
|
INT_PTR |
Een Windows PVOID
, een C void*
, kan worden ge marshalld als een of IntPtr
UIntPtr
, maar liever void*
, indien mogelijk.
Voorheen ingebouwde ondersteunde typen
Er zijn zeldzame gevallen wanneer ingebouwde ondersteuning voor een type wordt verwijderd.
De UnmanagedType.HString
en UnmanagedType.IInspectable
ingebouwde marshal-ondersteuning is verwijderd in de .NET 5-release. U moet binaire bestanden die gebruikmaken van dit marshallingtype en die gericht zijn op een eerder framework opnieuw compileren. Het is nog steeds mogelijk om dit type te marshalen, maar u moet het handmatig marshalen, zoals in het volgende codevoorbeeld wordt weergegeven. Deze code werkt verder en is ook compatibel met eerdere frameworks.
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);
Overwegingen voor platformoverschrijdend gegevenstype
Er zijn typen in de C/C++-taal die breedtegraad hebben in de wijze waarop ze worden gedefinieerd. Bij het schrijven van platformoverschrijdende interoperabiliteit kunnen er gevallen ontstaan waarbij platforms verschillen en problemen kunnen veroorzaken als ze niet worden overwogen.
C/C++ long
C/C++ long
en C# long
zijn niet noodzakelijkerwijs dezelfde grootte.
Het long
type in C/C++ is gedefinieerd met ten minste 32 bits. Dit betekent dat er een minimum aantal vereiste bits is, maar platformen kunnen desgewenst meer bits gebruiken. De volgende tabel illustreert de verschillen in opgegeven bits voor het C/C++ long
-gegevenstype tussen platforms.
Platform | 32-bits | 64-bits |
---|---|---|
Windows | 32 | 32 |
macOS/*nix | 32 | 64 |
C# long
is daarentegen altijd 64-bits. Daarom kunt u het beste voorkomen dat c# long
wordt gebruikt om C/C++ long
te gebruiken.
(Dit probleem met C/C++ long
bestaat niet voor C/C++ char
, short
int
en long long
omdat ze respectievelijk 8, 16, 32 en 64 bits zijn op al deze platforms.)
Gebruik in .NET 6 en latere versies de CLong
en CULong
typen voor interop met C/C++ long
en unsigned long
gegevenstypen. Het volgende voorbeeld is bedoeld voorCLong
, maar u kunt dit op een vergelijkbare manier abstract unsigned long
makenCULong
.
// Cross platform C function
// long Function(long a);
[DllImport("NativeLib")]
extern static CLong Function(CLong a);
// Usage
nint result = Function(new CLong(10)).Value;
Wanneer u zich richt op .NET 5 en eerdere versies, moet u afzonderlijke Windows- en niet-Windows-handtekeningen declareren om het probleem op te lossen.
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);
}
Structs
Beheerde structs worden gemaakt op de stack en worden pas verwijderd als de methode wordt geretourneerd. Ze worden dan per definitie 'vastgemaakt' (het wordt niet verplaatst door de GC). U kunt het adres ook gewoon in onveilige codeblokken gebruiken als systeemeigen code de aanwijzer niet voorbij het einde van de huidige methode gebruikt.
Blittable structs zijn veel beter presterend omdat ze eenvoudig rechtstreeks door de marshalllaag kunnen worden gebruikt. Probeer structs blittable te maken (bijvoorbeeld vermijden bool
). Zie de sectie Blittable Types voor meer informatie.
Als de struct blittable is, gebruikt sizeof()
u deze in plaats van Marshal.SizeOf<MyStruct>()
voor betere prestaties. Zoals hierboven vermeld, kunt u valideren dat het type belicht is door een vastgemaakte GCHandle
pincode te maken. Als het type geen tekenreeks is of als blittable wordt beschouwd, GCHandle.Alloc
genereert u een ArgumentException
.
Aanwijzers naar instructies in definities moeten worden doorgegeven door of unsafe
gebruikt ref
en *
.
✔️ DO match the managed struct zo dicht mogelijk bij de shape en namen die worden gebruikt in de officiële platformdocumentatie of header.
✔️ GEBRUIK de C# sizeof()
in plaats van Marshal.SizeOf<MyStruct>()
voor belichte structuren om de prestaties te verbeteren.
❌ VERMIJD het gebruik van klassen om complexe systeemeigen typen uit te drukken via overname.
❌ VERMIJD het gebruik System.Delegate
of System.MulticastDelegate
de velden om functiepointervelden in structuren weer te geven.
Omdat System.Delegate en System.MulticastDelegate geen vereiste handtekening hebben, garanderen ze niet dat de gemachtigde die is doorgegeven, overeenkomt met de handtekening die de systeemeigen code verwacht. Daarnaast kan in .NET Framework en .NET Core het marshallen van een struct met een System.Delegate
of System.MulticastDelegate
van de systeemeigen weergave naar een beheerd object de runtime stabiliseren als de waarde van het veld in de systeemeigen weergave geen functieaanwijzer is die een beheerde gemachtigde verpakt. In .NET 5 en latere versies wordt het marshallen van een System.Delegate
of System.MulticastDelegate
veld van een systeemeigen weergave naar een beheerd object niet ondersteund. Gebruik een specifiek type gedelegeerde in plaats van System.Delegate
of System.MulticastDelegate
.
Vaste buffers
Een matrix zoals INT_PTR Reserved1[2]
moet worden ge marshalld naar twee IntPtr
velden, Reserved1a
en Reserved1b
. Wanneer de systeemeigen matrix een primitief type is, kunnen we het fixed
trefwoord gebruiken om het iets overzichtelijker te schrijven. Dit ziet er bijvoorbeeld SYSTEM_PROCESS_INFORMATION
als volgt uit in de systeemeigen header:
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
...
} SYSTEM_PROCESS_INFORMATION
In C# kunnen we het als volgt schrijven:
internal unsafe struct SYSTEM_PROCESS_INFORMATION
{
internal uint NextEntryOffset;
internal uint NumberOfThreads;
private fixed byte Reserved1[48];
internal Interop.UNICODE_STRING ImageName;
...
}
Er zijn echter enkele gotcha's met vaste buffers. Vaste buffers van niet-belichte typen worden niet correct ge marshalld, dus de in-place matrix moet worden uitgebreid naar meerdere afzonderlijke velden. Bovendien, in .NET Framework en .NET Core vóór 3.0, als een struct met een vast bufferveld is genest in een niet-belichte struct, wordt het vaste bufferveld niet correct ge marshalld naar systeemeigen code.