Delen via


Structuur marshalling aanpassen

Soms zijn de standaard marshallregels voor structuren niet precies wat u nodig hebt. De .NET-runtimes bieden enkele uitbreidingspunten om de indeling van uw structuur aan te passen en hoe velden worden marshalled. 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-marshalling is uitgeschakeld, moet elk veld marshalling handmatig worden uitgevoerd.

Notitie

Dit artikel heeft geen betrekking op het aanpassen van marshalling voor door de bron gegenereerde 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 waar mogelijk gebruik te maken LayoutKind.Sequential .

✔️ DO only use LayoutKind.Explicit in marshalling when your native struct also has an explicit layout, such as a union.

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

❌ VERMIJD het gebruik LayoutKind.Explicit bij het marshallen van structuren op niet-Windows-platforms als u runtimes vóór .NET Core 3.0 wilt targeten. De .NET Core-runtime vóór 3.0 biedt geen ondersteuning voor het doorgeven van expliciete structuren op systeemeigen functies op Intel- of AMD 64-bits niet-Windows-systemen. De runtime biedt echter ondersteuning voor het doorgeven van expliciete structuren op alle platforms.

Booleaanse veld marshalling aanpassen

Systeemeigen code heeft veel verschillende Booleaanse representaties. In 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. In de volgende voorbeelden ziet u hoe u marshal .NET bool kunt gebruiken voor verschillende systeemeigen Booleaanse typen.

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 behulp van de UnmanagedType.U1 onderstaande waarden UnmanagedType.I1 kunt u de runtime vertellen het veld te marshalen b als een systeemeigen bool type van 1 byte.

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

In Windows kunt u de UnmanagedType.VariantBool waarde gebruiken om de runtime te vertellen dat u uw Booleaanse waarde wilt instellen op een 2-bytewaarde VARIANT_BOOL :

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

Notitie

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.

.NET marshals-matrices zijn standaard een 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 mogelijk marshal matrices als SAFEARRAY* objecten gebruiken. U kunt de System.Runtime.InteropServices.MarshalAsAttribute en de UnmanagedType.SafeArray waarde gebruiken om de runtime te vertellen dat een matrix 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];
};

Notitie

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

Tekenreeksveld marshalling aanpassen

.NET biedt ook een groot aantal aanpassingen voor marshallvelden.

.NET marshals standaard een tekenreeks als een aanwijzer naar een door null beëindigde tekenreeks. 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 tekenreeksen wilt marshalen met behulp van de UTF-8-codering, kunt u de UnmanagedType.LPUTF8Str waarde in uw MarshalAsAttribute.

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

Notitie

Voor het gebruik is UnmanagedType.LPUTF8Str .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 als een BSTRtekenreeks gebruiken. Met behulp van de UnmanagedType.BStr waarde kunt u een tekenreeks als een BSTR.

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 als een marshalal HSTRINGgebruiken. Met behulp van de UnmanagedType.HString waarde kunt u een tekenreeks als een HSTRING. HSTRING marshalling wordt alleen ondersteund voor 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 in de structuur doorgeeft, kunt u de UnmanagedType.ByValTStr waarde gebruiken. Houd er rekening mee dat de codering voor een tekenreeks die door ByValTStr een tekenreeks is ge marshalld, wordt bepaald op basis van het CharSet kenmerk. 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.
};

Het marshallen van decimalen aanpassen

Als u met Windows werkt, kunt u enkele API's tegenkomen die gebruikmaken van de systeemeigen CY structuur of CURRENCY structuur. Standaard is het .NET-type decimal marshals 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

In Windows kunt u marshal object-getypte velden naar systeemeigen code. U kunt deze velden tot een van de drie typen marshalen:

Standaard wordt een object-getypt veld ge marshalld op een IUnknown* veld dat het object verpakt.

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 marshal VARIANTwilt gebruiken, voegt u een MarshalAsAttribute met de UnmanagedType.Struct waarde toe.

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

In de volgende tabel wordt beschreven hoe verschillende runtimetypen van het obj veld worden toegewezen aan de verschillende typen die zijn opgeslagen in een VARIANT:

.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