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 SAFEARRAY
element bevindt, kunt u de MarshalAsAttribute.SafeArraySubType en MarshalAsAttribute.SafeArrayUserDefinedSubType velden gebruiken om het exacte elementtype van het SAFEARRAY
element 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 BSTR
tekenreeks 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 HSTRING
gebruiken. 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 VARIANT
wilt 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 |