Примечание.
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Иногда правила маршаллинга структур, используемые по умолчанию, не совсем соответствуют вашим требованиям. Среды выполнения .NET предоставляют несколько возможностей для настройки компоновки структуры и того, как выполняется маршалинг полей. Настройка структуры размещения поддерживается для всех сценариев, но настройка маршаллинга полей поддерживается только для сценариев, в которых включён маршалинг во время выполнения. Если маршалирование среды выполнения отключено, то любое маршалирование полей должно выполняться вручную.
Замечание
В этой статье не описывается настройка маршалинга для межоперационного взаимодействия, создаваемого генератором исходного кода. Если вы используете interop, генерируемое исходным кодом, для вызовов P/Invoke или COM, см. настройка маршаллинга.
Настройка макета структуры
.NET предоставляет System.Runtime.InteropServices.StructLayoutAttribute атрибут и System.Runtime.InteropServices.LayoutKind перечисление, чтобы настроить способ размещения полей в памяти. Приведенные ниже рекомендации помогут избежать распространенных проблем.
✔️ Рекомендуется использовать LayoutKind.Sequential каждый раз, когда это возможно.
✔️ Используйте LayoutKind.Explicit только при маршалинге, если ваша неуправляемая структура также имеет явное расположение полей, например объединение.
❌ Избегайте использования классов для выражения сложных собственных типов с помощью наследования.
❌Избегайте использования LayoutKind.Explicit при маршаллинговых структурах на платформах, отличных от Windows, если необходимо использовать целевые среды выполнения до .NET Core 3.0. Среда выполнения .NET Core до версии 3.0 не поддерживает передачу явных структур по значению в собственные функции в системах Intel или AMD 64-разрядных систем, отличных от Windows. Однако среда выполнения поддерживает передачу явных структур по ссылке на всех платформах.
Буферы фиксированного размера и встроенные массивы
Если необходимо определить буфер фиксированного размера в структуре, не используйте StructLayoutAttribute.Size свойство для выделения дополнительного пространства. Свойство Size управляет неуправляемой структурой размещения типа (структурой, используемой при вызове Marshal.StructureToPtr) и влияет на управляемую структуру размещения только для blittable-типов. Для неблиттируемых типов среда выполнения выделяет память только под фактические поля, что может привести к повреждению памяти, если попытаться использовать это дополнительное пространство.
❌ Избегайте использования StructLayoutAttribute.Size для создания буферов фиксированного размера в управляемых типах.
✔️ Используйте System.Runtime.CompilerServices.InlineArrayAttribute для определения буферов фиксированного размера в современных версиях .NET (C# 12 и более поздних версиях).
В следующем примере показан неправильный подход, который может привести к повреждению памяти:
// ❌ 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 для создания буфера правильного размера:
// ✔️ 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
}
}
}
Дополнительные сведения о встроенных массивах см. в справочнике по языку C#.
Настройка маршалинга полей типа Boolean
Машинный код имеет множество различных представлений булевых значений. Только в Windows существует три способа представления булевых значений. Среда выполнения не знает нативного определения вашей структуры, поэтому в лучшем случае она может лишь предположить, как следует маршалировать ваши логические значения. Среда выполнения .NET позволяет указать, как выполнять маршалинг вашего логического поля. В следующих примерах показано, как выполнять маршалинг .NET bool в различные собственные типы Boolean.
Логические значения по умолчанию маршалируются как собственное 4-байтовое значение Win32 BOOL, как показано в следующем примере:
public struct WinBool
{
public bool b;
}
struct WinBool
{
public BOOL b;
};
Если вы хотите явно указать это, можно использовать значение UnmanagedType.Bool, чтобы получить такое же поведение, как и выше:
public struct WinBool
{
[MarshalAs(UnmanagedType.Bool)]
public bool b;
}
struct WinBool
{
public BOOL b;
};
Используя указанные ниже значения UnmanagedType.U1 или UnmanagedType.I1, можно указать среде выполнения маршалировать поле b как собственный 1-байтовый тип bool.
public struct CBool
{
[MarshalAs(UnmanagedType.U1)]
public bool b;
}
struct CBool
{
public bool b;
};
В Windows можно использовать значение UnmanagedType.VariantBool, чтобы указать среде выполнения маршалировать логическое значение в 2-байтовое значение VARIANT_BOOL:
public struct VariantBool
{
[MarshalAs(UnmanagedType.VariantBool)]
public bool b;
}
struct VariantBool
{
public VARIANT_BOOL b;
};
Замечание
VARIANT_BOOL отличается от большинства типов bool тем, что VARIANT_TRUE = -1 и VARIANT_FALSE = 0. Кроме того, все значения, которые не равны VARIANT_TRUE , считаются ложными.
Настройка маршаллинга полей массива
.NET также предусматривает несколько способов настройки маршалинга массивов.
По умолчанию .NET маршалирует массивы в виде указателя на непрерывный список элементов:
public struct DefaultArray
{
public int[] values;
}
struct DefaultArray
{
int32_t* values;
};
При взаимодействии с COM-API может потребоваться маршалировать массивы в виде SAFEARRAY* объектов. Можно использовать значения System.Runtime.InteropServices.MarshalAsAttribute и UnmanagedType.SafeArray, чтобы указать среде выполнения маршалировать массив как SAFEARRAY*:
public struct SafeArrayExample
{
[MarshalAs(UnmanagedType.SafeArray)]
public int[] values;
}
struct SafeArrayExample
{
SAFEARRAY* values;
};
Если вам нужно настроить, какой тип элемента находится в SAFEARRAY, можно использовать поля MarshalAsAttribute.SafeArraySubType и MarshalAsAttribute.SafeArrayUserDefinedSubType, чтобы задать точный тип элемента для SAFEARRAY.
Если вам нужно маршалировать массив на месте, можно использовать значение UnmanagedType.ByValArray, чтобы указать средству маршалирования маршалировать массив на месте. При использовании этого типа маршалинга необходимо также указать в поле MarshalAsAttribute.SizeConst значение, соответствующее количеству элементов в массиве, чтобы среда выполнения могла правильно выделить память под структуру.
public struct InPlaceArray
{
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 4)]
public int[] values;
}
struct InPlaceArray
{
int values[4];
};
Замечание
.NET не поддерживает маршаллирование поля массива переменной длины как член гибкого массива C99.
Настройка маршаллинга строковых полей
.NET также предоставляет широкие возможности настройки при маршалинге строковых полей.
По умолчанию .NET упаковывает строку как указатель на строку с завершающим нулевым символом. Кодировка зависит от значения StructLayoutAttribute.CharSet поля в поле System.Runtime.InteropServices.StructLayoutAttribute. Если атрибут не указан, кодировка по умолчанию использует кодировку 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.
};
Если вам нужно использовать разные кодировки для разных полей или вы просто предпочитаете более явно указать это в определении структуры, можно использовать значения UnmanagedType.LPStr или UnmanagedType.LPWStr в атрибуте 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.
};
Если вы хотите использовать кодировку UTF-8 при маршалинге строк, вы можете использовать значение UnmanagedType.LPUTF8Str в MarshalAsAttribute.
public struct UTF8String
{
[MarshalAs(UnmanagedType.LPUTF8Str)]
public string str;
}
struct UTF8String
{
char* str;
};
Замечание
Для использования UnmanagedType.LPUTF8Str требуется .NET Framework 4.7 (или более поздних версий) или .NET Core 1.1 (или более поздних версий). Он недоступен в .NET Standard 2.0.
Если вы работаете с COM-API, может потребоваться маршалировать строку в виде BSTR. Используя значение UnmanagedType.BStr, можно маршалировать строку как BSTR.
public struct BString
{
[MarshalAs(UnmanagedType.BStr)]
public string str;
}
struct BString
{
BSTR str;
};
При использовании API на основе WinRT может потребоваться маршалировать строку в виде HSTRING. Используя значение UnmanagedType.HString, можно маршалировать строку как HSTRING.
HSTRING Маршаллирование поддерживается только в средах выполнения со встроенной поддержкой WinRT. Поддержка WinRT была удалена в .NET 5, поэтому HSTRINGмаршалинг не поддерживается в .NET 5 и более поздних версиях.
public struct HString
{
[MarshalAs(UnmanagedType.HString)]
public string str;
}
struct BString
{
HSTRING str;
};
Если ваш API требует передавать строку непосредственно в структуре, можно использовать значение UnmanagedType.ByValTStr. Обратите внимание, что кодировка строки, маршалируемой с помощью ByValTStr, определяется по атрибуту CharSet. Кроме того, требуется, чтобы длина строки передавалась полем 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.
};
Настройка маршаллинга десятичного поля
Если вы работаете над Windows, вы можете столкнуться с некоторыми API, которые используют собственные CY или CURRENCY структуры. По умолчанию тип .NET decimal маршалируется в собственную структуру DECIMAL. Однако можно использовать MarshalAsAttribute со значением UnmanagedType.Currency, чтобы указать маршалеру преобразовать значение decimal в собственное значение CY.
public struct Currency
{
[MarshalAs(UnmanagedType.Currency)]
public decimal dec;
}
struct Currency
{
CY dec;
};
Объединения
Объединение — это тип данных, который может содержать различные типы данных на вершине одной памяти. Это общая форма данных на языке C. Объединение может быть выражено в .NET с помощьюLayoutKind.Explicit. Рекомендуется использовать структуры при определении объединения в .NET. Использование классов может вызвать проблемы макета и привести к непредсказуемому поведению.
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;
}
}
Маршал System.Object
В Windows можно маршалировать поля типа object в неуправляемый код. Эти поля можно маршалировать в один из трёх типов:
По умолчанию поле типа object будет маршалировано в объект IUnknown*, который оборачивает этот объект.
public struct ObjectDefault
{
public object obj;
}
struct ObjectDefault
{
IUnknown* obj;
};
Если вы хотите маршалировать поле объекта в IDispatch*, добавьте MarshalAsAttribute со значением UnmanagedType.IDispatch.
public struct ObjectDispatch
{
[MarshalAs(UnmanagedType.IDispatch)]
public object obj;
}
struct ObjectDispatch
{
IDispatch* obj;
};
Если вы хотите маршалировать его как VARIANT, добавьте MarshalAsAttribute со значением UnmanagedType.Struct.
public struct ObjectVariant
{
[MarshalAs(UnmanagedType.Struct)]
public object obj;
}
struct ObjectVariant
{
VARIANT obj;
};
В следующей таблице описывается, как различные типы времени выполнения поля obj сопоставляются с различными типами, хранящимися в VARIANT:
| Тип .NET | Тип 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 |