Struktúra-sorozás testreszabása

Néha a struktúrák alapértelmezett rendezési szabályai nem pontosan azok, amelyekre szüksége van. A .NET futtatókörnyezetek néhány bővítménypontot biztosítanak a struktúra elrendezésének és a mezők elrendezésének testreszabásához. A struktúraelrendezés testreszabása minden forgatókönyv esetében támogatott, de a mezőrendezés testreszabása csak olyan forgatókönyvekben támogatott, ahol engedélyezve van a futásidejű rendezés. Ha a futásidejű rendezés le van tiltva, akkor minden mezőrendezést manuálisan kell elvégezni.

Megjegyzés:

Ez a cikk nem tér ki a forrásgenerált interoperabilitás marshalizálásának testreszabására. Ha P/Invokes vagy COM esetén forrásgenerált interoperabilitást használ, lásd az adattovábbítás testreszabása című témakört.

Struktúraelrendezés testreszabása

.NET megadja az System.Runtime.InteropServices.StructLayoutAttribute attribútumot és az System.Runtime.InteropServices.LayoutKind enumerálást, hogy testre szabhassa a mezők memóriabeli elhelyezésének módját. Az alábbi útmutató segít elkerülni a gyakori problémákat.

✔️ Lehetőség szerint használja a(z) LayoutKind.Sequential elemet.

✔️ A LayoutKind.Explicit elemet CSAK AKKOR használja az adatátadás során, ha a natív struktúra is explicit memóriakiosztással rendelkezik, például unió esetén.

❌ NE használjon osztályokat összetett natív típusok öröklés útján történő kifejezéséhez.

❌ Kerülje a LayoutKind.Explicit használatát a struktúrák marshalizálásakor nem Windows-platformokon, ha a .NET Core 3.0 előtti futtatókörnyezeteket is támogatnia kell. A 3.0 előtti .NET Core-futtatókörnyezet nem támogatja a explicit struktúrák érték szerinti átadását natív függvényeknek az Intel vagy az AMD 64 bites, nem Windows rendszereken. A futtatókörnyezet azonban minden platformon támogatja az explicit struktúrák hivatkozással történő átadását.

Rögzített méretű pufferek és beágyazott tömbök

Ha rögzített méretű puffert kell definiálnia egy struktúrában, ne használja a StructLayoutAttribute.Size tulajdonságot további hely lefoglalására. A(z) Size tulajdonság szabályozza a típus nem felügyelt elrendezését (a Marshal.StructureToPtr meghívásakor használt elrendezést), és csak a blittable típusok esetén van hatással a felügyelt elrendezésre. Nem blittable típusok esetén a futtatókörnyezet csak a tényleges mezők alapján foglal helyet, ami memóriakorrupcióhoz vezethet, ha megpróbálja használni a többletterületet.

StructLayoutAttribute.Size KERÜLJE a rögzített méretű pufferek létrehozását felügyelt típusok esetén.

✔️ A modern .NET-ben (C# 12 és újabb verziókban) a System.Runtime.CompilerServices.InlineArrayAttribute használatával definiáljon rögzített méretű puffereket.

Az alábbi példa azt a helytelen módszert mutatja be, amely memóriasérüléshez vezethet:

// ❌ 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
        }
    }
}

InlineArrayAttribute Ehelyett hozzon létre egy megfelelő méretű puffert:

// ✔️ 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
        }
    }
}

A beágyazott tömbökről további információt a C# nyelvi referenciájában talál.

A logikai mezők átalakításának testreszabása

A natív kód számos különböző logikai megjelenítéssel rendelkezik. Csak Windows három módon jelölheti a logikai értékeket. A futtatókörnyezet nem ismeri a struktúra natív definícióját, ezért a legjobb megoldás, ha kitalálja, hogyan hozhatja létre a logikai értékeket. A .NET-futtatókörnyezet módot biztosít annak megadására, hogy a logikai mező marshalingja hogyan történjen. Az alábbi példák bemutatják, hogyan képezhető le a .NET bool különböző natív logikai típusokra.

A logikai értékek alapértelmezés szerint natív, 4 bájtos Win32 BOOL értékként marshalelódnak, ahogy az alábbi példa mutatja:

public struct WinBool
{
    public bool b;
}
struct WinBool
{
    public BOOL b;
};

Ha explicit szeretne lenni, az UnmanagedType.Bool érték használatával ugyanazt a viselkedést érheti el, mint a fentiekben:

public struct WinBool
{
    [MarshalAs(UnmanagedType.Bool)]
    public bool b;
}
struct WinBool
{
    public BOOL b;
};

Az alábbi UnmanagedType.U1 vagy UnmanagedType.I1 értékek használatával megadhatja a futtatókörnyezetnek, hogy a b mezőt 1 bájtos natív bool típusként kezelje.

public struct CBool
{
    [MarshalAs(UnmanagedType.U1)]
    public bool b;
}
struct CBool
{
    public bool b;
};

A Windows az UnmanagedType.VariantBool érték használatával megadhatja a futtatókörnyezetnek, hogy a logikai értéket 2 bájtos VARIANT_BOOL értékre alkalmazza:

public struct VariantBool
{
    [MarshalAs(UnmanagedType.VariantBool)]
    public bool b;
}
struct VariantBool
{
    public VARIANT_BOOL b;
};

Megjegyzés:

VARIANT_BOOL abban különbözik a legtöbb bool típustól, hogy VARIANT_TRUE = -1 és VARIANT_FALSE = 0. Emellett minden olyan érték, amely nem egyenlő, VARIANT_TRUE hamisnak minősül.

Tömbmező-rendezés testreszabása

.NET a tömbrendezés testreszabásának néhány módját is tartalmazza.

Alapértelmezés szerint a .NET a tömböket az elemek egybefüggő listájára mutató mutatóként rendezi át:

public struct DefaultArray
{
    public int[] values;
}
struct DefaultArray
{
    int32_t* values;
};

Ha COM API-kat használ, előfordulhat, hogy a tömböket SAFEARRAY* objektumokként kell marshaloznia. A System.Runtime.InteropServices.MarshalAsAttribute és a UnmanagedType.SafeArray értékek használatával megadhatja a futtatókörnyezetnek, hogy egy tömböt SAFEARRAY* típusként kezeljen:

public struct SafeArrayExample
{
    [MarshalAs(UnmanagedType.SafeArray)]
    public int[] values;
}
struct SafeArrayExample
{
    SAFEARRAY* values;
};

Ha testre szeretné szabni, hogy milyen típusú elem van a(z) SAFEARRAY elemben, akkor a MarshalAsAttribute.SafeArraySubType és a MarshalAsAttribute.SafeArrayUserDefinedSubType mezővel testre szabhatja a SAFEARRAY pontos elemtípusát.

Ha a tömböt helyben kell marshalni, a UnmanagedType.ByValArray értékkel megadhatja a marshalernek, hogy a tömböt helyben marshalja. Ha ezt az adatrendezést használja, a MarshalAsAttribute.SizeConst mezőnek is meg kell adnia egy értéket, amely a tömb elemeinek számát jelzi, hogy a futtatókörnyezet megfelelően helyet tudjon lefoglalni a struktúra számára.

public struct InPlaceArray
{
    [MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
    public int[] values;
}
struct InPlaceArray
{
    int values[4];
};

Megjegyzés:

.NET nem támogatja a változó hosszúságú tömbmezők C99 rugalmas tömbtagként való elrendezését.

Sztringmező-rendezés testreszabása

A .NET emellett számos testreszabási lehetőséget biztosít a karakterláncmezők marshalingjához.

Alapértelmezés szerint a .NET a sztringet nullával lezárt sztringre mutató mutatóként marshalálja. A kódolás a(z) System.Runtime.InteropServices.StructLayoutAttributeStructLayoutAttribute.CharSet mezőjének értékétől függ. Ha nincs megadva attribútum, a kódolás alapértelmezés szerint ANSI-kódolást használ.

[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.
};

Ha különböző mezőkhöz különböző kódolásokat kell használnia, vagy egyszerűen csak egyértelműbben szeretné megadni a struct definíciójában, használhatja az UnmanagedType.LPStr vagy UnmanagedType.LPWStr értékeket egy System.Runtime.InteropServices.MarshalAsAttribute attribútumon.

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.
};

Ha a sztringjeit UTF-8 kódolással szeretné marshalelni, használhatja a(z) MarshalAsAttributeUnmanagedType.LPUTF8Str értékét.

public struct UTF8String
{
    [MarshalAs(UnmanagedType.LPUTF8Str)]
    public string str;
}
struct UTF8String
{
    char* str;
};

Megjegyzés:

A használathoz UnmanagedType.LPUTF8Str .NET Framework 4.7 (vagy újabb verziók) vagy .NET Core 1.1 (vagy újabb verziók) szükséges. Nem érhető el .NET Standard 2.0-s verzióban.

Ha COM API-kkal dolgozik, előfordulhat, hogy egy sztringet BSTR típusként kell kezelnie. A UnmanagedType.BStr érték használatával egy sztringet BSTR típusként kezelhet.

public struct BString
{
    [MarshalAs(UnmanagedType.BStr)]
    public string str;
}
struct BString
{
    BSTR str;
};

WinRT-alapú API használatakor előfordulhat, hogy egy sztringet HSTRING típusként kell marshalnia. Az UnmanagedType.HString érték használatával egy sztringet HSTRING típusként rendezhet. HSTRING a marshalizálás csak beépített WinRT-támogatással rendelkező futtatókörnyezetekben támogatott. A WinRT-támogatást eltávolították a .NET 5-ben, így a HSTRING adatátrendezés a .NET 5-ben és az újabb verziókban nem támogatott.

public struct HString
{
    [MarshalAs(UnmanagedType.HString)]
    public string str;
}
struct BString
{
    HSTRING str;
};

Ha az API megköveteli, hogy helyben adja át a sztringet a struktúrában, használhatja az UnmanagedType.ByValTStr értéket. Vegye figyelembe, hogy a ByValTStr által rendezett karakterlánc kódolását a CharSet attribútum határozza meg. Emellett a karakterlánc hosszát a MarshalAsAttribute.SizeConst mezőben kell megadni.

[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.
};

A decimális mező sorosításának testreszabása

Ha Windowson dolgozik, találkozhat olyan API-kkal, amelyek a natív CY vagy CURRENCY szerkezetet használják. Alapértelmezés szerint a .NET decimal típus a natív DECIMAL struktúrának felel meg. Azonban a MarshalAsAttribute elemet a UnmanagedType.Currency értékkel használva utasíthatja a marshalert, hogy egy decimal értéket natív CY értékké alakítson át.

public struct Currency
{
    [MarshalAs(UnmanagedType.Currency)]
    public decimal dec;
}
struct Currency
{
    CY dec;
};

Szakszervezetek

Az unió olyan adattípus, amely különböző típusú adatokat képes ugyanazon a memóriaterületen tárolni. Ez a C nyelven gyakran használt adatforma. A egyesítés .NET használatával LayoutKind.Explicitfejezhető ki. A .NET egyesítésének meghatározásakor ajánlott a szerkezetek használata. Az osztályok használata elrendezési problémákat okozhat, és kiszámíthatatlan viselkedést eredményezhet.

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;
    }
}

Marsall System.Object

Windows rendszerben a object típusú mezőket natív kódhoz rendezheti. Ezeket a mezőket három típus egyikére helyezheti el:

Alapértelmezés szerint egy object típusú mező egy olyan IUnknown* típussá konvertálódik, amely beburkolja az objektumot.

public struct ObjectDefault
{
    public object obj;
}
struct ObjectDefault
{
    IUnknown* obj;
};

Ha egy objektummezőt IDispatch* elemre szeretne szerializálni, adjon hozzá egy MarshalAsAttribute elemet a UnmanagedType.IDispatch értékkel.

public struct ObjectDispatch
{
    [MarshalAs(UnmanagedType.IDispatch)]
    public object obj;
}
struct ObjectDispatch
{
    IDispatch* obj;
};

Ha azt VARIANT típusként szeretné kezelni, adjon hozzá egy MarshalAsAttribute elemet a(z) UnmanagedType.Struct értékkel.

public struct ObjectVariant
{
    [MarshalAs(UnmanagedType.Struct)]
    public object obj;
}
struct ObjectVariant
{
    VARIANT obj;
};

Az alábbi táblázat bemutatja, hogy a obj mező különböző futásidejű típusai hogyan feleltethetők meg a VARIANT mezőben tárolt különféle típusoknak:

.NET-típus VARIANT típus
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