Megosztás a következőn keresztül:


Struktúra-rendezé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 rendezési módjának 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.

Feljegyzés

Ez a cikk nem foglalkozik a forrás által létrehozott interophoz tartozó rendezés testreszabásával. Ha forrás által létrehozott interop-ot használ a P/Invokes vagy a COM számára, tekintse meg a rendezés testreszabását.

Struktúraelrendezés testreszabása

A .NET biztosítja 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ét. Az alábbi útmutató segít elkerülni a gyakori problémákat.

✔️ FONTOLJA meg, LayoutKind.Sequential hogy amikor csak lehetséges.

✔️ A DO csak akkor használható LayoutKind.Explicit a rendezés során, ha a natív szerkezet explicit elrendezéssel is rendelkezik, például egy egyesítéssel.

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

❌ NE használja LayoutKind.Explicit a struktúrák nem Windows-platformokon történő rendezésekor, ha a futtatókörnyezeteket a .NET Core 3.0 előtt kell megcélolnia. 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 Intel vagy AMD 64 bites, nem Windows rendszerű rendszereken. A futtatókörnyezet azonban minden platformon támogatja az explicit struktúrák átadását.

Logikai mezőrendezés testreszabása

A natív kód számos különböző logikai megjelenítéssel rendelkezik. Csak Windows rendszeren háromféleképpen jelölhet 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 segítségével jelezheti, hogyan hozhatja létre a logikai mezőt. Az alábbi példák azt mutatják be, hogyan lehet a .NET-et bool különböző natív logikai típusokra létrehozni.

A logikai értékek alapértelmezés szerint natív 4 bájtos Win32-értékként BOOL lesznek rendezve az alábbi példában látható módon:

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 UnmanagedType.U1 alábbi értékek használatával UnmanagedType.I1 megadhatja a futtatókörnyezetnek, hogy 1 bájtos natív bool típusként alkalmazza a b mezőt.

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

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

Feljegyzés

VARIANT_BOOL különbözik, mint a legtöbb bool típusok, 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

A .NET néhány módszert is tartalmaz a tömbrendezés testreszabására.

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

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

Ha COM API-kkal együttműködik, előfordulhat, hogy objektumként SAFEARRAY* kell létrehoznia a tömböket. Az és az System.Runtime.InteropServices.MarshalAsAttributeUnmanagedType.SafeArray érték használatával megadhatja a futtatókörnyezetnek, hogy egy tömböt a következőként SAFEARRAY*adjon meg:

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

Ha testre kell szabnia, hogy milyen típusú elem található a SAFEARRAYfájlban, akkor a MarshalAsAttribute.SafeArraySubType mezőkkel MarshalAsAttribute.SafeArrayUserDefinedSubType testre szabhatja a pontos elemtípust SAFEARRAY.

Ha helyben kell létrehoznia a tömböt, az UnmanagedType.ByValArray érték használatával megadhatja a rendezőnek, hogy helyben helyezze el a tömböt. Ha ezt a rendezést használja, a tömb elemeinek számához is meg kell adnia egy értéket MarshalAsAttribute.SizeConst a mezőnek, hogy a futtatókörnyezet megfelelően lefoglalhassa a térközt a szerkezet számára.

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

Feljegyzés

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

Sztringmező-rendezés testreszabása

A .NET a sztringmezők beállításához is számos testreszabást biztosít.

Alapértelmezés szerint a .NET egy sztringet egy null értékű sztringre mutató mutatóként hoz létre. A kódolás a mező értékétől függ a StructLayoutAttribute.CharSetSystem.Runtime.InteropServices.StructLayoutAttribute. 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ő kódolásokat kell használnia a különböző mezőkhöz, vagy inkább explicitebb a szerkezetdefinícióban, használhatja az UnmanagedType.LPStr attribútum értékeit vagy UnmanagedType.LPWStr értékeit System.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.
};

Ha az UTF-8 kódolással szeretné feloldani a sztringeket, használhatja a UnmanagedType.LPUTF8Str benne lévő MarshalAsAttributeértéket.

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

Feljegyzés

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

Ha COM API-kkal dolgozik, előfordulhat, hogy sztringet BSTRkell létrehoznia. Az UnmanagedType.BStr érték használatával egy sztringet BSTRlétrehozhat.

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

WinRT-alapú API használata esetén előfordulhat, hogy sztringet HSTRINGkell létrehoznia. Az UnmanagedType.HString érték használatával egy sztringet HSTRINGlétrehozhat. HSTRING a rendezés csak beépített WinRT-támogatással rendelkező futtatókörnyezetekben támogatott. A WinRT-támogatás el lett távolítva a .NET 5-ben, így HSTRING a rendezés nem támogatott a .NET 5-ben vagy újabb verzióban.

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 sztringek kódolását ByValTStr az CharSet attribútum határozza meg. Emellett a mezőnek sztringhosszt kell megadnia 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.
};

Decimális mezőrendezés testreszabása

Ha Windowson dolgozik, előfordulhat, hogy a natív CY vagy CURRENCY a struktúrát használó API-k is megjelennek. Alapértelmezés szerint a .NET decimal típusú marshalok a natív DECIMAL struktúrára épülnek. Az érték használatával UnmanagedType.Currency azonban utasíthatja MarshalAsAttribute a rendezőt, hogy alakítson át egy decimal értéket natív CY értékké.

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

Szakszervezetek

Az egyesítő adatok olyan adattípusok, amelyek különböző típusú adatokat tartalmazhatnak ugyanazon a memórián. Ez a C nyelven gyakran használt adatforma. Az egyesítők .NET-ben kifejezhetők a .NET használatával LayoutKind.Explicit. A .NET-beli egyesítések definiálá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 rendszeren a - objecttyped mezőket natív kódba helyezheti. Ezeket a mezőket három típus egyikére helyezheti el:

Alapértelmezés szerint egy object-typed mező lesz rendezve egy IUnknown* olyanhoz, amely körbefuttatja az objektumot.

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

Ha objektummezőt szeretne egy objektummezőbe IDispatch*helyezni, adjon hozzá egy MarshalAsAttributeUnmanagedType.IDispatch értéket.

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

Ha meg szeretné adni VARIANT, adjon hozzá egy MarshalAsAttributeUnmanagedType.Struct értéket.

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

Az alábbi táblázat azt ismerteti, hogy a obj mező különböző futásidejű típusai hogyan képezik le a különböző, a következőben tárolt típusokat VARIANT:

.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