Notatka
Dostęp do tej strony wymaga autoryzacji. Może spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Czasami domyślne reguły marshalingu dla struktur nie są dokładnie tym, czego potrzebujesz. Środowiska uruchomieniowe .NET udostępniają kilka punktów rozszerzenia, które umożliwiają dostosowanie układu struktury i sposobu rozmieszczenia pól. Dostosowywanie układu struktury jest obsługiwane we wszystkich scenariuszach, ale dostosowywanie marszalizacji pól jest obsługiwane tylko w scenariuszach, w których włączona jest marszalizacja w czasie wykonywania. Jeśli marshalowanie w czasie wykonywania jest wyłączone, każde marshalowanie pól musi zostać wykonane ręcznie.
Uwaga / Notatka
Ten artykuł nie omawia dostosowywania marshalingu dla współdziałania generowanego przez źródła. Jeśli używasz międzyoperacyjności generowanej przez źródło dla wywołań P/Invoke lub COM, zobacz Dostosowywanie marshalingu.
Dostosowywanie układu struktury
Platforma .NET udostępnia atrybut System.Runtime.InteropServices.StructLayoutAttribute i wyliczenie System.Runtime.InteropServices.LayoutKind, umożliwiające dostosowanie sposobu rozmieszczania pól w pamięci. Poniższe wskazówki pomogą Uniknąć typowych problemów.
✔️ ROZWAŻ użycie LayoutKind.Sequential, gdy tylko jest to możliwe.
✔️ Używaj LayoutKind.Explicit tylko w marshalowaniu, gdy Twoja struktura natywna również ma jawny układ, na przykład unia.
❌ UNIKAJ używania klas do wyrażania złożonych typów natywnych za pośrednictwem dziedziczenia.
❌ Unikaj używania LayoutKind.Explicit podczas marshalowania struktur na platformach innych niż Windows, jeśli musisz obsługiwać środowiska uruchomieniowe sprzed platformy .NET Core 3.0. Środowisko uruchomieniowe .NET Core przed 3.0 nie obsługuje przekazywania jawnych struktur według wartości do funkcji natywnych w systemach intel lub AMD 64-bitowych, nie Windows. Jednak środowisko uruchomieniowe obsługuje przekazywanie jawnych struktur przez odwołanie na wszystkich platformach.
Bufory o stałym rozmiarze i tablice inline
Jeśli musisz zdefiniować bufor o stałym rozmiarze w strukturze, nie należy używać StructLayoutAttribute.Size właściwości do przydzielania dodatkowego miejsca. Właściwość Size określa niezarządzany układ typu (układ używany podczas wywoływania Marshal.StructureToPtr) i wpływa na układ zarządzany tylko w przypadku typów blittable. W przypadku typów innych niż blittable środowisko uruchomieniowe przydziela tylko miejsce na podstawie rzeczywistych pól, co może prowadzić do uszkodzenia pamięci, jeśli próbujesz użyć dodatkowego miejsca.
❌ UNIKAJ tworzenia StructLayoutAttribute.Size buforów o stałym rozmiarze w typach zarządzanych.
✔️ UŻYWAJ System.Runtime.CompilerServices.InlineArrayAttribute do definiowania buforów o stałym rozmiarze w nowoczesnym środowisku .NET (C# 12 i nowszych wersjach).
W poniższym przykładzie przedstawiono niepoprawne podejście, które może prowadzić do uszkodzenia pamięci:
// ❌ 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
}
}
}
Zamiast tego użyj polecenia InlineArrayAttribute , aby utworzyć bufor o prawidłowym rozmiarze:
// ✔️ 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
}
}
}
Aby uzyskać więcej informacji na temat tablic wbudowanych, zobacz dokumentację języka C#.
Dostosowywanie marshalingu pól logicznych
Kod natywny ma wiele różnych reprezentacji logicznych. W samym systemie Windows istnieją trzy sposoby reprezentowania wartości logicznych. Środowisko uruchomieniowe nie zna natywnej definicji twojej struktury, więc może co najwyżej zgadywać, jak przeprowadzić marshaling wartości typu Boolean. Środowisko uruchomieniowe .NET umożliwia określenie sposobu marshalingu pola typu Boolean. W poniższych przykładach pokazano marshaling .NET bool do różnych natywnych typów Boolean.
Wartości logiczne są domyślnie przekazywane jako natywna 4-bajtowa wartość Win32 BOOL, jak pokazano w poniższym przykładzie:
public struct WinBool
{
public bool b;
}
struct WinBool
{
public BOOL b;
};
Jeśli chcesz być precyzyjny, możesz użyć wartości UnmanagedType.Bool, aby uzyskać to samo zachowanie co powyżej:
public struct WinBool
{
[MarshalAs(UnmanagedType.Bool)]
public bool b;
}
struct WinBool
{
public BOOL b;
};
Używając poniższych wartości UnmanagedType.U1 lub UnmanagedType.I1, możesz poinstruować środowisko uruchomieniowe, aby traktowało pole b jako 1-bajtowy typ natywny bool.
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
W systemie Windows można użyć wartości UnmanagedType.VariantBool, aby poinformować środowisko uruchomieniowe, że ma marshalować wartość logiczną jako 2-bajtową wartość VARIANT_BOOL:
public struct VariantBool
{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}
struct VariantBool
{
public VARIANT_BOOL b;
};
Uwaga / Notatka
VARIANT_BOOL różni się od większości typów bool tym, że VARIANT_TRUE = -1 i VARIANT_FALSE = 0. Ponadto wszystkie wartości, które nie są równe VARIANT_TRUE , są uznawane za fałszywe.
Dostosowywanie marshalingu pól tablicy
Platforma .NET zawiera również kilka sposobów dostosowywania marshalingu tablic.
Domyślnie platforma .NET organizuje tablice w pamięci jako wskaźnik do ciągłego bloku elementów:
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
Jeśli korzystasz z interfejsów API COM, może być konieczne marshalowanie tablic jako obiektów SAFEARRAY*. Możesz użyć wartości System.Runtime.InteropServices.MarshalAsAttribute i UnmanagedType.SafeArray, aby wskazać środowisku uruchomieniowemu, że ma marshalować tablicę jako SAFEARRAY*:
public struct SafeArrayExample
{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}
struct SafeArrayExample
{
SAFEARRAY* values;
};
Jeśli musisz dostosować typ elementu w SAFEARRAY, możesz użyć pól MarshalAsAttribute.SafeArraySubType i MarshalAsAttribute.SafeArrayUserDefinedSubType, aby dostosować dokładny typ elementu SAFEARRAY.
Jeśli musisz marshalować tablicę w miejscu, możesz użyć wartości UnmanagedType.ByValArray, aby poinformować mechanizm marshalowania, że ma marshalować tablicę w miejscu. Podczas korzystania z tego mechanizmu marshalowania należy również podać wartość pola MarshalAsAttribute.SizeConst, określającą liczbę elementów w tablicy, aby środowisko uruchomieniowe mogło poprawnie przydzielić miejsce dla struktury.
public struct InPlaceArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] values;
}
struct InPlaceArray
{
int values[4];
};
Uwaga / Notatka
Platforma .NET nie obsługuje marshalowania pola tablicowego o zmiennej długości jako elastycznego elementu tablicowego C99.
Dostosowywanie marshalingu pól typu string
Platforma .NET oferuje również wiele opcji dostosowywania sposobu marshalowania pól typu string.
Domyślnie platforma .NET przekazuje ciąg jako wskaźnik do ciągu zakończonego znakiem null. Kodowanie zależy od wartości StructLayoutAttribute.CharSet pola w obiekcie System.Runtime.InteropServices.StructLayoutAttribute. Jeśli nie określono żadnego atrybutu, domyślnie używane jest kodowanie 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.
};
Jeśli musisz używać różnych kodowań dla różnych pól albo po prostu wolisz bardziej jednoznacznie określić definicję struktury, możesz użyć wartości UnmanagedType.LPStr lub UnmanagedType.LPWStr w atrybucie 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.
};
Jeśli chcesz marshalować ciągi znaków przy użyciu kodowania UTF-8, możesz użyć wartości UnmanagedType.LPUTF8Str w elemencie MarshalAsAttribute.
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
Uwaga / Notatka
Korzystanie z UnmanagedType.LPUTF8Str programu wymaga .NET Framework 4.7 (lub nowszych wersji) lub .NET Core 1.1 (lub nowszych wersji). Nie jest ona dostępna w wersji .NET Standard 2.0.
Jeśli pracujesz z interfejsami API COM, może być konieczne zamarshalingowanie ciągu znaków jako BSTR. Za pomocą wartości UnmanagedType.BStr można przekazać ciąg jako BSTR.
public struct BString
{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}
struct BString
{
BSTR str;
};
W przypadku korzystania z interfejsu API opartego na technologii WinRT może być konieczne przeprowadzanie marshalingu ciągu jako elementu HSTRING. Używając wartości UnmanagedType.HString, można dokonać marshalingu ciągu jako HSTRING.
HSTRING marshalling jest obsługiwany tylko w środowiskach uruchomieniowych z wbudowaną obsługą WinRT. Obsługa WinRT została usunięta w platformie .NET 5, więc HSTRING marshalling nie jest obsługiwany w .NET 5 ani nowszych wersjach.
public struct HString
{
[MarshalAs(UnmanagedType.HString)]
public string str;
}
struct BString
{
HSTRING str;
};
Jeśli interfejs API wymaga przekazania ciągu znaków bezpośrednio w strukturze, możesz użyć wartości UnmanagedType.ByValTStr. Należy pamiętać, że kodowanie ciągu przekazanego przez ByValTStr jest określane na podstawie atrybutu CharSet. Ponadto wymaga, aby długość ciągu została przekazana przez pole 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.
};
Dostosowywanie marshalingu pól dziesiętnych
Jeśli pracujesz w systemie Windows, możesz napotkać niektóre interfejsy API, które używają natywnej struktury CY lub CURRENCY. Domyślnie typ .NET decimal jest mapowany na natywną strukturę DECIMAL. Jednak można użyć MarshalAsAttribute o wartości UnmanagedType.Currency, aby poinstruować marshalera, by przekonwertował wartość decimal na natywną wartość CY.
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Związki zawodowe
Związek to typ danych, który może zawierać różne typy danych na szczycie tej samej pamięci. Jest to typowa forma danych w języku C. Unii można wyrazić w .NET przy użyciu polecenia LayoutKind.Explicit. Zaleca się używanie struktur podczas definiowania unii w .NET. Użycie klas może powodować problemy z układem i powodować nieprzewidywalne zachowanie.
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;
}
}
Marszałek System.Object
W systemie Windows można przekazywać pola typu object do kodu natywnego. Te pola można przekształcić do jednego z trzech typów:
Domyślnie pole typu object zostanie zserializowane do obiektu IUnknown*, opakowującego ten obiekt.
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
Jeśli chcesz serializować pole obiektu do IDispatch*, dodaj element MarshalAsAttribute o wartości UnmanagedType.IDispatch.
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
Jeśli chcesz zorganizować je jako VARIANT, dodaj element MarshalAsAttribute o wartości UnmanagedType.Struct.
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
Poniższa tabela opisuje, w jaki sposób różne typy środowiska uruchomieniowego pola obj są mapowane na różne typy przechowywane w obiekcie VARIANT:
| typ .NET | TYP WARIANTU |
|---|---|
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 |