Sdílet prostřednictvím


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 objectpole typu -typed do nativního kódu. Tato pole můžete zařadovat na jeden ze tří typů:

Ve výchozím nastavení objectbude 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 VARIANThodnotu, 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