Anpassa strukturr marshalling
Ibland är standardreglerna för marshalling för strukturer inte precis vad du behöver. .NET-körningen innehåller några tilläggspunkter som du kan använda för att anpassa strukturens layout och hur fälten är ordnade. Anpassning av strukturlayout stöds för alla scenarier, men anpassning av fältrundning stöds endast för scenarier där körningsrundning är aktiverat. Om runtime-marshalling är inaktiverat måste alla fältrundanvisning utföras manuellt.
Kommentar
Den här artikeln beskriver inte anpassning av marshalling för källgenererad interop. Om du använder källgenererad interop för P/Invokes eller COM kan du läsa anpassa marshalling.
Anpassa strukturlayout
.NET tillhandahåller System.Runtime.InteropServices.StructLayoutAttribute attributet och System.Runtime.InteropServices.LayoutKind uppräkningen så att du kan anpassa hur fält placeras i minnet. Följande vägledning hjälper dig att undvika vanliga problem.
✔️ ÖVERVÄG att använda LayoutKind.Sequential
när det är möjligt.
✔️ Använd endast LayoutKind.Explicit
i marshalling när din interna struct också har en explicit layout, till exempel en union.
❌ UNDVIK att använda klasser för att uttrycka komplexa inbyggda typer genom arv.
❌ UNDVIK att använda LayoutKind.Explicit
när du samlar strukturer på plattformar som inte är Windows-plattformar om du behöver rikta in dig på runtimes före .NET Core 3.0. .NET Core-körningen före 3.0 stöder inte överföring av explicita strukturer efter värde till inbyggda funktioner i Intel- eller AMD 64-bitars system som inte är Windows-system. Körningen har dock stöd för att skicka explicita strukturer med referens på alla plattformar.
Anpassa boolesk fältr marshalling
Intern kod har många olika booleska representationer. Enbart i Windows finns det tre sätt att representera booleska värden. Körningen känner inte till den interna definitionen av din struktur, så det bästa den kan göra är att gissa hur du konverterar dina booleska värden. Med .NET-körningen kan du ange hur du konverterar det booleska fältet. I följande exempel visas hur du konverterar .NET bool
till olika interna booleska typer.
Booleska värden är standard för marshalling som ett ursprungligt Win32-värde BOOL
på 4 byte enligt följande exempel:
public struct WinBool
{
public bool b;
}
struct WinBool
{
public BOOL b;
};
Om du vill vara explicit kan du använda UnmanagedType.Bool värdet för att få samma beteende som ovan:
public struct WinBool
{
[MarshalAs(UnmanagedType.Bool)]
public bool b;
}
struct WinBool
{
public BOOL b;
};
UnmanagedType.U1
Med hjälp av värdena nedan UnmanagedType.I1
kan du be körningen att konvertera b
fältet som en intern bool
typ av 1 byte.
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
I Windows kan du använda UnmanagedType.VariantBool värdet för att instruera körningen att konvertera det booleska värdet till ett värde på 2 byte VARIANT_BOOL
:
public struct VariantBool
{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}
struct VariantBool
{
public VARIANT_BOOL b;
};
Kommentar
VARIANT_BOOL
skiljer sig från de flesta booltyper i det VARIANT_TRUE = -1
och VARIANT_FALSE = 0
. Dessutom anses alla värden som inte är lika med VARIANT_TRUE
vara falska.
Anpassa matrisfältsr marshalling
.NET innehåller också några sätt att anpassa matrisrappning.
Som standard konverterar .NET matriser som en pekare till en sammanhängande lista över elementen:
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
Om du interagerar med COM-API:er kan du behöva konvertera matriser som SAFEARRAY*
objekt. Du kan använda System.Runtime.InteropServices.MarshalAsAttribute värdet och UnmanagedType.SafeArray för att instruera körningen att konvertera en matris som :SAFEARRAY*
public struct SafeArrayExample
{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}
struct SafeArrayExample
{
SAFEARRAY* values;
};
Om du behöver anpassa vilken typ av element som finns i SAFEARRAY
kan du använda fälten MarshalAsAttribute.SafeArraySubType och MarshalAsAttribute.SafeArrayUserDefinedSubType för att anpassa den exakta elementtypen för SAFEARRAY
.
Om du behöver marskalka matrisen på plats kan du använda UnmanagedType.ByValArray värdet för att be marshaller att konvertera matrisen på plats. När du använder den här marshallingen måste du också ange ett värde i MarshalAsAttribute.SizeConst fältet för antalet element i matrisen så att körningen kan allokera utrymme för strukturen korrekt.
public struct InPlaceArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] values;
}
struct InPlaceArray
{
int values[4];
};
Kommentar
.NET stöder inte marshalling av ett matrisfält med variabel längd som en C99-medlem för flexibel matris.
Anpassa strängfältsriffning
.NET tillhandahåller också en mängd olika anpassningar för att ordna strängfält.
Som standard konverterar .NET en sträng som pekare till en null-avslutad sträng. Kodningen beror på värdet för StructLayoutAttribute.CharSet fältet i System.Runtime.InteropServices.StructLayoutAttribute. Om inget attribut anges anges kodningen som standard till en ANSI-kodning.
[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.
};
Om du behöver använda olika kodningar för olika fält eller bara föredrar att vara mer explicit i din struct-definition kan du använda UnmanagedType.LPStr värdena eller UnmanagedType.LPWStr för ett System.Runtime.InteropServices.MarshalAsAttribute attribut.
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.
};
Om du vill konvertera strängarna med hjälp av UTF-8-kodningen kan du använda UnmanagedType.LPUTF8Str värdet i .MarshalAsAttribute
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
Kommentar
Användning UnmanagedType.LPUTF8Str kräver antingen .NET Framework 4.7 (eller senare versioner) eller .NET Core 1.1 (eller senare versioner). Den är inte tillgänglig i .NET Standard 2.0.
Om du arbetar med COM-API:er kan du behöva konvertera en sträng som en BSTR
. Med hjälp av värdet UnmanagedType.BStr kan du konvertera en sträng som en BSTR
.
public struct BString
{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}
struct BString
{
BSTR str;
};
När du använder ett WinRT-baserat API kan du behöva konvertera en sträng som en HSTRING
. Med hjälp av värdet UnmanagedType.HString kan du konvertera en sträng som en HSTRING
. HSTRING
marshalling stöds endast på runtimes med inbyggt WinRT-stöd. WinRT-stöd har tagits bort i .NET 5, så HSTRING
marshalling stöds inte i .NET 5 eller senare.
public struct HString
{
[MarshalAs(UnmanagedType.HString)]
public string str;
}
struct BString
{
HSTRING str;
};
Om ditt API kräver att du skickar strängen på plats i strukturen kan du använda UnmanagedType.ByValTStr värdet. Observera att kodningen för en sträng som är kodad efter ByValTStr
bestäms av CharSet
attributet. Dessutom kräver det att en stränglängd skickas av fältet MarshalAsAttribute.SizeConst .
[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.
};
Anpassa decimalfältsr marshalling
Om du arbetar med Windows kan du stöta på vissa API:er som använder den inbyggda CY
strukturen eller CURRENCY
strukturen. Som standard konverterar .NET-typen decimal
till den interna DECIMAL
strukturen. Du kan dock använda en MarshalAsAttribute med UnmanagedType.Currency värdet för att instruera marshaller att konvertera ett decimal
värde till ett internt CY
värde.
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Fackföreningar
En union är en datatyp som kan innehålla olika typer av data ovanpå samma minne. Det är en vanlig form av data på C-språket. En union kan uttryckas i .NET med .LayoutKind.Explicit
Vi rekommenderar att du använder structs när du definierar en union i .NET. Användning av klasser kan orsaka layoutproblem och skapa oförutsägbara beteenden.
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;
}
}
Marskalk System.Object
I Windows kan du konvertera object
fält som skrivits till intern kod. Du kan konvertera dessa fält till en av tre typer:
Som standard kommer ett object
-typat fält att ordnas till ett IUnknown*
som omsluter objektet.
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
Om du vill konvertera ett objektfält till ett IDispatch*
lägger du till ett MarshalAsAttribute med UnmanagedType.IDispatch värdet .
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
Om du vill konvertera den som en VARIANT
lägger du till en MarshalAsAttribute med UnmanagedType.Struct värdet .
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
I följande tabell beskrivs hur olika körningstyper av fältkartan obj
till de olika typerna som lagras i en VARIANT
:
.NET-typ | VARIANTtyp |
---|---|
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 |