Маршалинг типов
Маршалинг — это процесс преобразования типов при переходе от управляемого кода к машинному.
Необходимость в маршалинге вызвана различием типов в управляемом и неуправляемом коде. Например, в управляемом коде есть string
неуправляемые строки, в то время как неуправляемые строки могут быть кодировкой .NET string
(UTF-16), кодировкой кодов ANSI, кодировкой UTF-8, null-terminated, ASCII и т. д. По умолчанию подсистема P/Invoke пытается выполнить правильные действия на основе поведения по умолчанию, описанного в этой статье. Однако в ситуациях, когда требуется дополнительный контроль, можно применить атрибут MarshalAs, чтобы указать ожидаемый тип на стороне неуправляемого кода. Например, если вы хотите, чтобы строка была отправлена в виде строки, завершаемой значением NULL, UTF-8, это можно сделать следующим образом:
[LibraryImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPStr)] string parameter);
// or
[LibraryImport("somenativelibrary.dll", StringMarshalling = StringMarshalling.Utf8)]
static extern int MethodB(string parameter);
Если атрибут применяется System.Runtime.CompilerServices.DisableRuntimeMarshallingAttribute
к сборке, правила в следующем разделе не применяются. Сведения о том, как значения .NET предоставляются машинным кодом при применении этого атрибута, см. в разделе "Отключено маршалирование среды выполнения".
Правила маршалинга общих типов
Обычно среда выполнения пытается маршалировать типы "правильно", чтобы от пользователя не потребовалось каких-либо действий. В приведенных ниже таблицах показано, как при использовании в параметре или поле каждый тип данных маршалируется по умолчанию. Чтобы проверить правильность данных в приведенных ниже таблицах для всех платформ, используются символьные и целочисленные (фиксированной ширины) типы данных C99/C++11. Используйте любой собственный тип, который имеет такое же выравнивание и требования к размеру, как эти типы данных.
В первой таблице представлены сопоставления различных типов, для которых маршалинг выполняется одинаково — как для P/Invoke, так и для поля.
Ключевое слово C# | Тип .NET | Собственный тип |
---|---|---|
byte |
System.Byte |
uint8_t |
sbyte |
System.SByte |
int8_t |
short |
System.Int16 |
int16_t |
ushort |
System.UInt16 |
uint16_t |
int |
System.Int32 |
int32_t |
uint |
System.UInt32 |
uint32_t |
long |
System.Int64 |
int64_t |
ulong |
System.UInt64 |
uint64_t |
char |
System.Char |
Либо char в char16_t зависимости от кодировки P/Invoke или структуры. См. дополнительные сведения в документации по кодировке. |
System.Char |
Либо char* в char16_t* зависимости от кодировки P/Invoke или структуры. См. дополнительные сведения в документации по кодировке. |
|
nint |
System.IntPtr |
intptr_t |
nuint |
System.UIntPtr |
uintptr_t |
Типы указателей .NET (например) void* |
void* |
|
Тип, производный от System.Runtime.InteropServices.SafeHandle |
void* |
|
Тип, производный от System.Runtime.InteropServices.CriticalHandle |
void* |
|
bool |
System.Boolean |
Тип Win32 BOOL |
decimal |
System.Decimal |
Структура COM DECIMAL |
Делегат .NET | Собственный указатель функции | |
System.DateTime |
Тип Win32 DATE |
|
System.Guid |
Тип Win32 GUID |
Если вы маршалируете тип как параметр или структуру, несколько категорий маршалинга будут иметь разные значения по умолчанию.
Тип .NET | Собственный тип (параметр) | Собственный тип (поле) |
---|---|---|
Массив .NET | Указатель на начало массива в собственном представлении элементов массива. | Нельзя использовать без атрибута [MarshalAs] |
Класс, где для LayoutKind задано значение Sequential или Explicit |
Указатель на собственное представление класса | Собственное представление класса |
В приведенной ниже таблице содержатся правила маршалинга, применяемые по умолчанию только для Windows. На других платформах (не Windows) эти типы нельзя маршалировать.
Тип .NET | Собственный тип (параметр) | Собственный тип (поле) |
---|---|---|
System.Object |
VARIANT |
IUnknown* |
System.Array |
Интерфейс COM | Нельзя использовать без атрибута [MarshalAs] |
System.ArgIterator |
va_list |
Не разрешенный |
System.Collections.IEnumerator |
IEnumVARIANT* |
Не разрешенный |
System.Collections.IEnumerable |
IDispatch* |
Не разрешенный |
System.DateTimeOffset |
int64_t — число тактов начиная с полуночи 1 января 1601 года. |
int64_t — число тактов начиная с полуночи 1 января 1601 года. |
Некоторые типы можно маршалировать только как параметры и нельзя — как поля. Эти типы перечислены в следующей таблице.
Тип .NET | Собственный тип (только параметр) |
---|---|
System.Text.StringBuilder |
Тип char* или char16_t* в зависимости от кодировки CharSet P/Invoke. См. дополнительные сведения в документации по кодировке. |
System.ArgIterator |
va_list (только в Windows x86/x64/arm64) |
System.Runtime.InteropServices.ArrayWithOffset |
void* |
System.Runtime.InteropServices.HandleRef |
void* |
Если эти значения по умолчанию вам не подходят, вы можете настроить маршалинг параметров. В статье о маршалинге параметров описана настройка маршалинга различных типов параметров.
Маршалирование по умолчанию в сценариях COM
При вызове методов в COM-объектах в .NET среда выполнения .NET изменяет правила маршаллинга по умолчанию в соответствии с общей семантикой COM. В следующей таблице перечислены правила, которые среда выполнения .NET использует в сценариях COM:
Тип .NET | Собственный тип (вызовы метода COM) |
---|---|
System.Boolean |
VARIANT_BOOL |
StringBuilder |
LPWSTR |
System.String |
BSTR |
Типы делегатов | _Delegate* в .NET Framework. Не разрешены в .NET Core и .NET 5 и более поздних версий. |
System.Drawing.Color |
OLECOLOR |
Массив .NET | SAFEARRAY |
System.String[] |
SAFEARRAY BSTR |
Маршалинг классов и структур
Другим аспектом маршалинга типов является способ передачи структуры в неуправляемый метод. Например, некоторые неуправляемые методы требуют указания структуры в качестве параметра. В таких случаях необходимо создать соответствующую структуру или соответствующий класс в управляемом коде и использовать в качестве параметра. Однако просто определение класса недостаточно, вам также нужно указать маршаллеру, как сопоставить поля в классе с неуправляемой структурой. Здесь пригодится атрибут StructLayout
.
[LibraryImport("kernel32.dll")]
static partial void GetSystemTime(out SystemTime systemTime);
[StructLayout(LayoutKind.Sequential)]
struct SystemTime
{
public ushort Year;
public ushort Month;
public ushort DayOfWeek;
public ushort Day;
public ushort Hour;
public ushort Minute;
public ushort Second;
public ushort Millisecond;
}
public static void Main(string[] args)
{
SystemTime st = new SystemTime();
GetSystemTime(st);
Console.WriteLine(st.Year);
}
В приведенном выше примере представлен код вызова функции GetSystemTime()
. Интерес представляет строка 4. Атрибут указывает, что поля класса должны последовательно сопоставляться со структурой на другой (неуправляемой) стороне. Это означает, что имена полей не важны, важен только их порядок, так как он должен соответствовать неуправляемой структуре, которая показана в приведенном ниже примере:
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME;
Иногда маршалинг по умолчанию не решает задачу для вашей структуры. В статье по настройке структуры маршаллинга вы узнаете, как настроить способ маршаллирования структуры.