Delen via


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+.
    • Er zijn gevallen waarin het gebruik [DllImport] van toepassing is. Een codeanalyse met id-SYSLIB1054 geeft aan wanneer dat het geval is.
  • ✔️ 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 is unsigned 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.
  • ✔️ 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:

❌ 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:

  1. Maak een StringBuilder van de gewenste capaciteit (wijst beheerde capaciteit toe) {1}.
  2. Aanroepen:
    1. Wijst een systeemeigen buffer {2}toe.
    2. Hiermee kopieert u de inhoud als [In] (de standaardwaarde voor een StringBuilder parameter).
    3. Kopieert de systeemeigen buffer naar een zojuist toegewezen beheerde matrix als [Out] {3} (ook de standaardwaarde voorStringBuilder).
  3. 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 default CoTaskMemFree to free strings or SysStringFree for strings that are marked as UnmanagedType.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. _BoolDe 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, ushortsbyte, , int, uint, , singleulonglongdouble
  • 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

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

NIET afgesplitsbaar:

  • bool

SOMS blittable:

  • char

Typen met soms belichte inhoud:

  • string

Wanneer blittable-typen worden doorgegeven door verwijzing met in, refof, of outwanneer 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.

charis 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 GCHandleinhoud 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.

Windows-gegevenstypen

Gegevenstypebereiken

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++ longte gebruiken.

(Dit probleem met C/C++ long bestaat niet voor C/C++ char, shortinten 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 GCHandlepincode 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.