Přizpůsobení zařazování struktur
Někdy výchozí pravidla pro řazení struktur nejsou přesně to, co potřebujete. Moduly runtime .NET poskytují několik bodů rozšíření, které vám umožní přizpůsobit rozložení struktury a způsob zařazování polí. Přizpůsobení rozložení struktury je podporováno pro všechny scénáře, ale přizpůsobení zařazování polí je podporováno pouze pro scénáře, ve kterých je povoleno zařazování za běhu. Pokud je zařazování za běhu zakázané, je nutné provést ruční zařazování polí.
Poznámka:
Tento článek se nezabývá přizpůsobením zařazování pro zdrojově vygenerovanou interoperabilitu. Pokud používáte zdrojově vygenerovanou interoperabilitu pro volání nespravovaných zpráv nebo com, podívejte se na přizpůsobení zařazování.
Přizpůsobení rozložení struktury
.NET poskytuje System.Runtime.InteropServices.StructLayoutAttribute atribut a System.Runtime.InteropServices.LayoutKind výčet, které vám umožní přizpůsobit způsob umístění polí do paměti. Následující doprovodné materiály vám pomůžou vyhnout se běžným problémům.
✔️ ZVAŽTE použití LayoutKind.Sequential
, kdykoli je to možné.
✔️ Pokud má vaše nativní struktura také explicitní rozložení, jako je sjednocení, použijte LayoutKind.Explicit
pouze při zařazování.
❌ Vyhněte se použití tříd k vyjádření složitých nativních typů prostřednictvím dědičnosti.
❌ Vyhněte se použití LayoutKind.Explicit
při zařazování struktur na platformách mimo Windows, pokud potřebujete cílit moduly runtime před .NET Core 3.0. Modul runtime .NET Core před verzí 3.0 nepodporuje předávání explicitních struktur podle hodnoty nativním funkcím v systémech Intel nebo AMD 64bitových systémů bez Windows. Modul runtime však podporuje předávání explicitních struktur odkazem na všech platformách.
Přizpůsobení logického zařazování polí
Nativní kód má mnoho různých logických reprezentací. V samotných Windows existují tři způsoby, jak znázorňovat logické hodnoty. Modul runtime nezná nativní definici vaší struktury, takže nejlepší možností je odhadnout, jak zařaďte logické hodnoty. Modul runtime .NET poskytuje způsob, jak určit, jak zařaďte logické pole. Následující příklady ukazují, jak zařazovat .NET bool
do různých nativních logických typů.
Logické hodnoty se standardně zařaďují jako nativní 4bajtů win32 BOOL
hodnoty, jak je znázorněno v následujícím příkladu:
public struct WinBool
{
public bool b;
}
struct WinBool
{
public BOOL b;
};
Pokud chcete být explicitní, můžete pomocí UnmanagedType.Bool hodnoty získat stejné chování jako výše:
public struct WinBool
{
[MarshalAs(UnmanagedType.Bool)]
public bool b;
}
struct WinBool
{
public BOOL b;
};
Pomocí následujících UnmanagedType.U1
hodnot UnmanagedType.I1
můžete modulu runtime sdělit, aby pole zařadil b
jako nativní bool
typ 1 bajtu.
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
Ve Windows můžete hodnotu použít UnmanagedType.VariantBool k tomu, aby modul runtime zařadil logickou hodnotu na 2 bajtovou VARIANT_BOOL
hodnotu:
public struct VariantBool
{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}
struct VariantBool
{
public VARIANT_BOOL b;
};
Poznámka:
VARIANT_BOOL
se liší od většiny typů bool v tom VARIANT_TRUE = -1
a VARIANT_FALSE = 0
. Všechny hodnoty, které se nerovnají VARIANT_TRUE
, se navíc považují za nepravdivé.
Přizpůsobení zařazování polí pole
.NET obsahuje také několik způsobů přizpůsobení zařazování polí.
Ve výchozím nastavení zařazuje pole .NET jako ukazatel na souvislý seznam prvků:
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
Pokud spolupracujete s rozhraními COM API, možná budete muset zařazuje pole jako SAFEARRAY*
objekty. Pomocí a UnmanagedType.SafeArray hodnoty můžete System.Runtime.InteropServices.MarshalAsAttribute modulu runtime sdělit, aby zařazování pole zařazování jakoSAFEARRAY*
:
public struct SafeArrayExample
{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}
struct SafeArrayExample
{
SAFEARRAY* values;
};
Pokud potřebujete přizpůsobit, jaký typ prvku je v objektu SAFEARRAY
, můžete použít MarshalAsAttribute.SafeArraySubType pole a MarshalAsAttribute.SafeArrayUserDefinedSubType přizpůsobit přesný typ elementu SAFEARRAY
.
Pokud potřebujete zařaďovat pole na místě, můžete použít UnmanagedType.ByValArray hodnotu k tomu, aby marshaller zařadil pole na místě. Při použití tohoto zařazování musíte také zadat hodnotu pole MarshalAsAttribute.SizeConst pro počet prvků v poli, aby modul runtime mohl správně přidělit prostor pro strukturu.
public struct InPlaceArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] values;
}
struct InPlaceArray
{
int values[4];
};
Poznámka:
.NET nepodporuje zařazování pole s proměnnou délkou pole jako člen flexibilního pole C99.
Přizpůsobení zařazování polí řetězců
.NET také poskytuje širokou škálu přizpůsobení pro zařazování řetězcových polí.
Ve výchozím nastavení zařazuje .NET řetězec jako ukazatel na řetězec ukončený hodnotou null. Kódování závisí na hodnotě StructLayoutAttribute.CharSet pole v poli System.Runtime.InteropServices.StructLayoutAttribute. Pokud není zadán žádný atribut, kódování se ve výchozím nastavení nastaví na kódování ANSI.
[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.
};
Pokud potřebujete použít různá kódování pro různá pole nebo chcete být explicitnější v definici struktury, můžete použít UnmanagedType.LPStr hodnoty atributu UnmanagedType.LPWStrSystem.Runtime.InteropServices.MarshalAsAttribute .
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.
};
Pokud chcete zařaďte řetězce pomocí kódování UTF-8, můžete použít UnmanagedType.LPUTF8Str hodnotu v MarshalAsAttributesouboru .
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
Poznámka:
Použití UnmanagedType.LPUTF8Str vyžaduje rozhraní .NET Framework 4.7 (nebo novější verze) nebo .NET Core 1.1 (nebo novější verze). Není k dispozici v .NET Standard 2.0.
Pokud pracujete s rozhraními COM API, možná budete muset zařazuje řetězec jako řetězec .BSTR
UnmanagedType.BStr Pomocí hodnoty můžete zařadovat řetězec jako BSTR
.
public struct BString
{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}
struct BString
{
BSTR str;
};
Při použití rozhraní API založeného na WinRT možná budete muset zařadit řetězec jako řetězec .HSTRING
UnmanagedType.HString Pomocí hodnoty můžete zařadovat řetězec jako HSTRING
. HSTRING
Marshalling se podporuje jenom v modulech runtime s integrovanou podporou WinRT. Podpora WinRT byla odebrána v .NET 5, takže HSTRING
se seřazování nepodporuje v .NET 5 nebo novějším.
public struct HString
{
[MarshalAs(UnmanagedType.HString)]
public string str;
}
struct BString
{
HSTRING str;
};
Pokud vaše rozhraní API vyžaduje předání řetězce na místě ve struktuře, můžete tuto hodnotu použít UnmanagedType.ByValTStr . Všimněte si, že kódování řetězce zařazovaného ByValTStr
podle je určeno z atributu CharSet
. Kromě toho vyžaduje, aby pole předalo délku MarshalAsAttribute.SizeConst řetězce.
[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.
};
Přizpůsobení zařazování desetinných míst
Pokud pracujete ve Windows, můžete narazit na některá rozhraní API, která používají nativní CY
nebo CURRENCY
strukturu. Ve výchozím nastavení zařazuje typ .NET decimal
do nativní DECIMAL
struktury. Pomocí hodnoty s hodnotou však MarshalAsAttribute můžete dát marshalleru pokyn, aby hodnotu převeďte decimal
na nativní CY
hodnotu.UnmanagedType.Currency
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Sjednocení
Sjednocení je datový typ, který může obsahovat různé typy dat na stejné paměti. Jedná se o běžnou formu dat v jazyce C. Sjednocení lze vyjádřit v .NET pomocí LayoutKind.Explicit
. Při definování sjednocení v .NET se doporučuje používat struktury. Použití tříd může způsobit problémy s rozložením a způsobit nepředvídatelné chování.
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;
}
}
Maršál System.Object
Ve Windows můžete zařadovat object
pole typu -typed do nativního kódu. Tato pole můžete zařadovat na jeden ze tří typů:
Ve výchozím nastavení object
bude pole typu -typed zařazováno do IUnknown*
objektu, který objekt zabalí.
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
Pokud chcete zařadíte pole objektu IDispatch*
do pole , přidejte hodnotu MarshalAsAttribute s UnmanagedType.IDispatch hodnotou.
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
Pokud ji chcete zařadit jako VARIANT
hodnotu, přidejte ji MarshalAsAttribute s UnmanagedType.Struct hodnotou.
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
Následující tabulka popisuje, jak různé typy obj
modulu runtime pole mapují na různé typy uložené v VARIANT
:
Typ .NET | TYP VARIANT |
---|---|
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 |