Poznámka:
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
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 marshalling za běhu zakázán, musí být jakýkoli marshalling polí proveden ručně.
Note
Tento článek se nezabývá přizpůsobením marshallingu pro propojování generované ze zdrojového kódu. Pokud používáte zdrojově generovanou interoperabilitu pro P/Invoke nebo COM, viz přizpůsobení marshalingu.
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é.
✔️ LayoutKind.Explicit používejte při marshalingu pouze tehdy, když má vaše nativní struktura také explicitní rozložení, například union.
❌ Vyhněte se použití tříd k vyjádření složitých nativních typů prostřednictvím dědičnosti.
❌ Nepoužívejte LayoutKind.Explicit při marshalingu struktur na jiných platformách než Windows, pokud potřebujete cílit na běhová prostředí starší než .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 bez Windows. Modul runtime však podporuje předávání explicitních struktur odkazem na všech platformách.
Vyrovnávací paměti s pevnou velikostí a vložená pole
Pokud potřebujete definovat vyrovnávací paměť s pevnou velikostí ve struktuře, nepoužívejte StructLayoutAttribute.Size vlastnost k přidělení nadbytečného místa. Vlastnost Size řídí nespravované rozložení typu (rozložení použité při volání Marshal.StructureToPtr) a ovlivňuje spravované rozložení pouze u blittable typů. U neblittovatelných typů runtime přiděluje prostor pouze podle skutečných polí, což může vést k poškození paměti, pokud se pokusíte použít dodatečný prostor.
❌ Vyhněte se používání StructLayoutAttribute.Size k vytváření vyrovnávacích pamětí s pevně danou velikostí ve spravovaných typech.
✔️ Použijte System.Runtime.CompilerServices.InlineArrayAttribute k definování vyrovnávacích pamětí s pevnou velikostí v moderním .NETu (C# 12 a novějším).
Následující příklad ukazuje nesprávný přístup, který může vést k poškození paměti:
// ❌ 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
}
}
}
Místo toho použijte InlineArrayAttribute k vytvoření vyrovnávací paměti s správnou velikostí:
// ✔️ 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
}
}
}
Další informace o vložených polích najdete v referenční dokumentaci jazyka C#.
Přizpůsobení logického zařazování polí
Nativní kód má mnoho různých logických reprezentací. Na Windows samotné existují tři způsoby, jak znázorňovat logické hodnoty. Runtime nezná nativní definici vaší struktury, takže může nanejvýš odhadnout, jak převést vaše logické hodnoty. Modul runtime .NET poskytuje způsob, jak určit, jak se má marshaling logického pole provést. Následující příklady ukazují, jak mapovat .NET bool na různé nativní typy Boolean.
Logické hodnoty se ve výchozím nastavení marshalují jako nativní 4bajtová hodnota Win32 BOOL, jak ukazuje následující příklad:
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íže uvedených hodnot UnmanagedType.U1 nebo UnmanagedType.I1 můžete prostředí runtime sdělit, aby pole b marshalovalo jako jednobajtový nativní typ bool.
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
Ve Windows můžete použít hodnotu UnmanagedType.VariantBool, aby běhové prostředí maršálovalo vaši logickou hodnotu jako 2bajtovou hodnotu VARIANT_BOOL:
public struct VariantBool
{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}
struct VariantBool
{
public VARIANT_BOOL b;
};
Note
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í marshalingu polí typu array
.NET také obsahuje několik způsobů přizpůsobení zařazování polí.
Ve výchozím nastavení platforma .NET marshaluje pole jako ukazatel na souvislou posloupnost prvků:
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
Pokud pracujete s rozhraními COM API, možná budete muset marshálovat pole jako objekty typu SAFEARRAY*. Pomocí hodnot System.Runtime.InteropServices.MarshalAsAttribute a UnmanagedType.SafeArray můžete modulu runtime sdělit, aby pole marshaloval jako SAFEARRAY*:
public struct SafeArrayExample
{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}
struct SafeArrayExample
{
SAFEARRAY* values;
};
Pokud potřebujete přizpůsobit typ prvku v SAFEARRAY, můžete pomocí polí MarshalAsAttribute.SafeArraySubType a MarshalAsAttribute.SafeArrayUserDefinedSubType přizpůsobit přesný typ prvku SAFEARRAY.
Pokud potřebujete marshálovat pole přímo na místě, můžete použít hodnotu UnmanagedType.ByValArray, aby marshaler marsháloval pole přímo na místě. Při použití tohoto zařazování je také nutné zadat hodnotu MarshalAsAttribute.SizeConst pole 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];
};
Note
.NET nepodporuje zařazování pole s proměnnou délkou pole jako člena flexibilního pole C99.
Přizpůsobení maršalování řetězcových polí
.NET také poskytuje širokou škálu možností přizpůsobení pro maršalování řetězcových polí.
Ve výchozím nastavení .NET marshaluje řetězec jako ukazatel na řetězec ukončený znakem 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 při marshalingu řetězců použít kódování UTF-8, můžete ve svém MarshalAsAttribute použít hodnotu UnmanagedType.LPUTF8Str.
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
Note
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 ve verzi .NET Standard 2.0.
Pokud pracujete s rozhraními COM API, možná budete muset marshalovat řetězec jako BSTR. Pomocí hodnoty UnmanagedType.BStr můžete marshálovat ř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 může být nutné marshalingovat řetězec jako HSTRING. Pomocí hodnoty UnmanagedType.HString můžete zapsat řetězec jako HSTRING.
HSTRING Marshaling je podporován pouze v běhových prostředích s integrovanou podporou WinRT. Podpora WinRT byla odebrána v .NET 5, takže HSTRINGmarshalling není v .NET 5 a novějších verzích podporováno.
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 přímo ve struktuře, můžete použít hodnotu UnmanagedType.ByValTStr. Všimněte si, že kódování řetězce marshalovaného pomocí ByValTStr určuje atribut CharSet. Kromě toho vyžaduje, aby byla délka řetězce předána v poli 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.
};
Přizpůsobení marshallingu pole typu decimal
Pokud pracujete na Windows, můžete narazit na některá rozhraní API, která používají nativní CY nebo CURRENCY strukturu. Typ .NET decimal se ve výchozím nastavení mapuje na nativní strukturu DECIMAL. Můžete však použít MarshalAsAttribute s hodnotou UnmanagedType.Currency, abyste marshalleru určili, aby převedl hodnotu decimal na nativní hodnotu CY.
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Odbory
Unie je datový typ, který může obsahovat různé typy dat ve stejné paměťové oblasti. 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 pole typu object předávat do nativního kódu. Tato pole můžete převést na jeden ze tří typů:
Ve výchozím nastavení bude pole typu object serializováno jako IUnknown*, který daný objekt obaluje.
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
Pokud chcete seřadit pole objektu do IDispatch*, přidejte MarshalAsAttribute s hodnotou UnmanagedType.IDispatch.
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
Chcete-li jej maršálovat jako VARIANT, přidejte MarshalAsAttribute s hodnotou UnmanagedType.Struct.
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
Následující tabulka popisuje, jak se různé typy pole obj za běhu 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 |