Structuurmarshalling aanpassen

Soms zijn de standaard marshallregels voor structuren niet precies wat u nodig hebt. De .NET-runtimes bieden enkele uitbreidingspunten waarmee u de indeling van uw structuur kunt aanpassen en de manier waarop velden worden gemarshald. Het aanpassen van de structuurindeling wordt ondersteund voor alle scenario's, maar het aanpassen van veld marshalling wordt alleen ondersteund voor scenario's waarbij runtime marshalling is ingeschakeld. Als runtime-marshaling is uitgeschakeld, moet alle marshaling van velden handmatig gebeuren.

Note

Dit artikel behandelt niet het aanpassen van marshalling voor broncodegegenereerde interop. Als u brongegenereerde interop gebruikt voor P/Invokes of COM, raadpleegt u het aanpassen van marshalling.

Structuurindeling aanpassen

.NET biedt het System.Runtime.InteropServices.StructLayoutAttribute kenmerk en de System.Runtime.InteropServices.LayoutKind opsomming waarmee u kunt aanpassen hoe velden in het geheugen worden geplaatst. De volgende richtlijnen helpen u veelvoorkomende problemen te voorkomen.

✔️ OVERWEEG om LayoutKind.Sequential waar mogelijk te gebruiken.

✔️ Gebruik LayoutKind.Explicit alleen bij marshalling wanneer uw native struct ook een expliciete indeling heeft, zoals een union.

❌ VERMIJD het gebruik van klassen om complexe systeemeigen typen uit te drukken via overerving.

❌ Vermijd het gebruik van LayoutKind.Explicit bij het marshallen van structuren op niet-Windows-platforms als u op runtimes van vóór .NET Core 3.0 moet mikken. De .NET Core-runtime vóór 3.0 biedt geen ondersteuning voor het doorgeven van expliciete structuren op basis van waarde aan systeemeigen functies op Intel- of AMD 64-bits niet-Windows systemen. De runtime ondersteunt echter op alle platforms het doorgeven van expliciete structuren op referentie.

Buffers met vaste grootte en inlinematrices

Als u een buffer met vaste grootte in een structuur wilt definiëren, gebruikt u de StructLayoutAttribute.Size eigenschap niet om extra ruimte toe te wijzen. De eigenschap Size bepaalt de niet-beheerde indeling van het type (de indeling die wordt gebruikt bij het aanroepen van Marshal.StructureToPtr) en is alleen van invloed op de beheerde indeling voor blittable typen. Voor niet-blittable typen wijst de runtime alleen geheugenruimte toe op basis van de daadwerkelijke velden, wat kan leiden tot geheugencorruptie als u de extra ruimte probeert te gebruiken.

❌ VERMIJD het gebruik StructLayoutAttribute.Size om buffers met een vaste grootte te maken in beheerde typen.

✔️ DO gebruiken System.Runtime.CompilerServices.InlineArrayAttribute voor het definiëren van buffers met een vaste grootte in moderne .NET (C# 12 en hoger).

In het volgende voorbeeld ziet u de onjuiste benadering die kan leiden tot beschadiging van het geheugen:

// ❌ DON'T: This is dangerous and can corrupt memory
[StructLayout(LayoutKind.Explicit, Size = 100)]
class Bad100ByteBuffer
{
    [FieldOffset(0)]
    private byte b;

    unsafe void UseBuffer()
    {
        fixed (byte* p = &b)
        {
            // Writing beyond the first byte corrupts memory
        }
    }
}

Gebruik in plaats daarvan InlineArrayAttribute om een buffer met de juiste grootte te maken:

// ✔️ DO: Use InlineArrayAttribute for fixed-size buffers
class Good100ByteBuffer
{
    [InlineArray(100)]
    private struct Buffer100
    {
        private byte _element0;
    }

    private Buffer100 buffer;

    unsafe void UseBuffer()
    {
        fixed (byte* p = &buffer[0])
        {
            // The buffer is guaranteed to be 100 bytes
        }
    }
}

Zie de C#-taalreferentie voor meer informatie over inlinematrices.

Aanpassen van het marshallen van Booleaanse velden

Systeemeigen code heeft veel verschillende Booleaanse representaties. Op Windows alleen zijn er drie manieren om Booleaanse waarden weer te geven. De runtime kent de systeemeigen definitie van uw structuur niet, dus het beste dat u kunt doen, is een schatting maken van hoe u uw Boole-waarden kunt marshalen. De .NET runtime biedt een manier om aan te geven hoe u uw Booleaanse veld marshalt. De volgende voorbeelden laten zien hoe u .NET bool marshalt naar verschillende natieve Booleaanse gegevenstypen.

Booleaanse waarden worden standaard ingesteld op marshalling als een systeemeigen win32-waarde BOOL van 4 bytes, zoals wordt weergegeven in het volgende voorbeeld:

public struct WinBool
{
    public bool b;
}
struct WinBool
{
    public BOOL b;
};

Als u expliciet wilt zijn, kunt u de UnmanagedType.Bool waarde gebruiken om hetzelfde gedrag te krijgen als hierboven:

public struct WinBool
{
    [MarshalAs(UnmanagedType.Bool)]
    public bool b;
}
struct WinBool
{
    public BOOL b;
};

Met de onderstaande waarden UnmanagedType.U1 of UnmanagedType.I1 kunt u de runtime instrueren het veld b te marshalen als een native bool-type van 1 byte.

public struct CBool
{
    [MarshalAs(UnmanagedType.U1)]
    public bool b;
}
struct CBool
{
    public bool b;
};

Op Windows kunt u de waarde UnmanagedType.VariantBool gebruiken om de runtime te instrueren uw Booleaanse waarde te marshalen naar een waarde van 2 bytes van het type VARIANT_BOOL:

public struct VariantBool
{
    [MarshalAs(UnmanagedType.VariantBool)]
    public bool b;
}
struct VariantBool
{
    public VARIANT_BOOL b;
};

Note

VARIANT_BOOL verschilt van de meeste booltypen in dat VARIANT_TRUE = -1 en VARIANT_FALSE = 0. Daarnaast worden alle waarden die niet gelijk zijn aan VARIANT_TRUE onwaar beschouwd.

Het marshallen van matrixvelden aanpassen

.NET bevat ook een aantal manieren om matrix marshalling aan te passen.

Standaard .NET marshals-matrices als aanwijzer naar een aaneengesloten lijst met de elementen:

public struct DefaultArray
{
    public int[] values;
}
struct DefaultArray
{
    int32_t* values;
};

Als u met COM-API's werkt, moet u arrays mogelijk marshalen als SAFEARRAY*-objecten. U kunt de System.Runtime.InteropServices.MarshalAsAttribute en de waarde UnmanagedType.SafeArray gebruiken om de runtime op te geven een array te marshalen als een SAFEARRAY*:

public struct SafeArrayExample
{
    [MarshalAs(UnmanagedType.SafeArray)]
    public int[] values;
}
struct SafeArrayExample
{
    SAFEARRAY* values;
};

Als u wilt aanpassen welk type element zich in het SAFEARRAYelement bevindt, kunt u de MarshalAsAttribute.SafeArraySubType en MarshalAsAttribute.SafeArrayUserDefinedSubType velden gebruiken om het exacte elementtype van het SAFEARRAYelement aan te passen.

Als u de matrix in-place wilt weergeven, kunt u de UnmanagedType.ByValArray waarde gebruiken om de marshaller te vertellen de matrix in-place te marshalen. Wanneer u deze marshalling gebruikt, moet u ook een waarde opgeven voor het MarshalAsAttribute.SizeConst veld voor het aantal elementen in de matrix, zodat de runtime ruimte voor de structuur correct kan toewijzen.

public struct InPlaceArray
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public int[] values;
}
struct InPlaceArray
{
    int values[4];
};

Note

.NET biedt geen ondersteuning voor het marshallen van een matrixveld voor variabele lengte als een C99 Flexible Array Member.

Marshaling van tekenreeksvelden aanpassen

.NET biedt ook een groot aantal aanpassingen voor marshallvelden.

Standaard marshalt .NET een tekenreeks als een aanwijzer naar een tekenreeks met null-terminatie. De codering is afhankelijk van de waarde van het StructLayoutAttribute.CharSet veld in de System.Runtime.InteropServices.StructLayoutAttribute. Als er geen kenmerk is opgegeven, wordt de codering standaard ingesteld op een ANSI-codering.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
    public string str;
}
struct DefaultString
{
    char* str;
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
    public string str;
}
struct DefaultString
{
    char16_t* str; // Could also be wchar_t* on Windows.
};

Als u verschillende coderingen voor verschillende velden wilt gebruiken of alleen explicieter wilt zijn in uw structdefinitie, kunt u de UnmanagedType.LPStr of UnmanagedType.LPWStr waarden voor een System.Runtime.InteropServices.MarshalAsAttribute kenmerk gebruiken.

public struct AnsiString
{
    [MarshalAs(UnmanagedType.LPStr)]
    public string str;
}
struct AnsiString
{
    char* str;
};
public struct UnicodeString
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string str;
}
struct UnicodeString
{
    char16_t* str; // Could also be wchar_t* on Windows.
};

Als u uw tekenreeksen wilt marshalen met UTF-8-codering, kunt u de waarde UnmanagedType.LPUTF8Str in uw MarshalAsAttribute gebruiken.

public struct UTF8String
{
    [MarshalAs(UnmanagedType.LPUTF8Str)]
    public string str;
}
struct UTF8String
{
    char* str;
};

Note

Voor het gebruik UnmanagedType.LPUTF8Str is .NET Framework 4.7 (of latere versies) of .NET Core 1.1 (of latere versies) vereist. Het is niet beschikbaar in .NET Standard 2.0.

Als u met COM-API's werkt, moet u mogelijk een tekenreeks marshallen als een BSTR. Met de UnmanagedType.BStr-waarde kunt u een tekenreeks naar een BSTR marshallen.

public struct BString
{
    [MarshalAs(UnmanagedType.BStr)]
    public string str;
}
struct BString
{
    BSTR str;
};

Wanneer u een op WinRT gebaseerde API gebruikt, moet u mogelijk een tekenreeks marshallen als een HSTRING. Met de waarde UnmanagedType.HString kunt u een tekenreeks marshallen als een HSTRING. HSTRING marshalling wordt alleen ondersteund op runtimes met ingebouwde WinRT-ondersteuning. WinRT-ondersteuning is verwijderd in .NET 5, dus HSTRING marshalling wordt niet ondersteund in .NET 5 of hoger.

public struct HString
{
    [MarshalAs(UnmanagedType.HString)]
    public string str;
}
struct BString
{
    HSTRING str;
};

Als uw API vereist dat u de tekenreeks rechtstreeks in de structuur moet doorgeven, kunt u de waarde UnmanagedType.ByValTStr gebruiken. Merk op dat de codering van een door ByValTStr gemarshald tekenreeks wordt bepaald door het kenmerk CharSet. Daarnaast is vereist dat een tekenreekslengte wordt doorgegeven door het MarshalAsAttribute.SizeConst veld.

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct DefaultString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string str;
}
struct DefaultString
{
    char str[4];
};
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct DefaultString
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 4)]
    public string str;
}
struct DefaultString
{
    char16_t str[4]; // Could also be wchar_t[4] on Windows.
};

Marshalling van decimale velden aanpassen

Als u aan Windows werkt, kunt u enkele API's tegenkomen die gebruikmaken van de systeemeigen CY structuur of CURRENCY structuur. Het .NET-type decimal wordt standaard gemarshald naar de systeemeigen DECIMAL-structuur. U kunt echter een MarshalAsAttribute met de UnmanagedType.Currency waarde gebruiken om de marshaller de opdracht te geven een decimal waarde te converteren naar een systeemeigen CY waarde.

public struct Currency
{
    [MarshalAs(UnmanagedType.Currency)]
    public decimal dec;
}
struct Currency
{
    CY dec;
};

Vakbonden

Een samenvoeging is een gegevenstype dat verschillende typen gegevens kan bevatten boven hetzelfde geheugen. Het is een algemene vorm van gegevens in de C-taal. Een samenvoeging kan worden uitgedrukt in .NET met behulp van LayoutKind.Explicit. Het is raadzaam om structs te gebruiken bij het definiëren van een samenvoeging in .NET. Het gebruik van klassen kan indelingsproblemen veroorzaken en onvoorspelbaar gedrag produceren.

struct device1_config
{
    void* a;
    void* b;
    void* c;
};
struct device2_config
{
    int32_t a;
    int32_t b;
};
struct config
{
    int32_t type;

    union
    {
        device1_config dev1;
        device2_config dev2;
    };
};
public unsafe struct Device1Config
{
    void* a;
    void* b;
    void* c;
}

public struct Device2Config
{
    int a;
    int b;
}

public struct Config
{
    public int Type;

    public _Union Anonymous;

    [StructLayout(LayoutKind.Explicit)]
    public struct _Union
    {
        [FieldOffset(0)]
        public Device1Config Dev1;

        [FieldOffset(0)]
        public Device2Config Dev2;
    }
}

Maarschalk System.Object

Op Windows kunt u velden van het type object marshallen naar native code. U kunt deze velden naar een van de drie typen converteren:

Standaard wordt een als object getypt veld gemarshald naar een IUnknown* die het object omhult.

public struct ObjectDefault
{
    public object obj;
}
struct ObjectDefault
{
    IUnknown* obj;
};

Als u een objectveld aan een IDispatch*objectveld wilt koppelen, voegt u een MarshalAsAttribute met de UnmanagedType.IDispatch waarde toe.

public struct ObjectDispatch
{
    [MarshalAs(UnmanagedType.IDispatch)]
    public object obj;
}
struct ObjectDispatch
{
    IDispatch* obj;
};

Als u het als een VARIANT wilt marshalen, voegt u een MarshalAsAttribute met de waarde UnmanagedType.Struct toe.

public struct ObjectVariant
{
    [MarshalAs(UnmanagedType.Struct)]
    public object obj;
}
struct ObjectVariant
{
    VARIANT obj;
};

In de volgende tabel wordt beschreven hoe verschillende runtime-typen van het veld obj overeenkomen met de verschillende typen die in een VARIANT zijn opgeslagen:

.NET type VARIANTtype
byte VT_UI1
sbyte VT_I1
short VT_I2
ushort VT_UI2
int VT_I4
uint VT_UI4
long VT_I8
ulong VT_UI8
float VT_R4
double VT_R8
char VT_UI2
string VT_BSTR
System.Runtime.InteropServices.BStrWrapper VT_BSTR
object VT_DISPATCH
System.Runtime.InteropServices.UnknownWrapper VT_UNKNOWN
System.Runtime.InteropServices.DispatchWrapper VT_DISPATCH
System.Reflection.Missing VT_ERROR
(object)null VT_EMPTY
bool VT_BOOL
System.DateTime VT_DATE
decimal VT_DECIMAL
System.Runtime.InteropServices.CurrencyWrapper VT_CURRENCY
System.DBNull VT_NULL