Megjegyzés
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhat bejelentkezni vagy módosítani a címtárat.
Az oldalhoz való hozzáféréshez engedély szükséges. Megpróbálhatja módosítani a címtárat.
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 |