Поделиться через


Маршалинг типов

Маршалинг — это процесс преобразования типов при переходе от управляемого кода к машинному.

Необходимость в маршалинге вызвана различием типов в управляемом и неуправляемом коде. Например, в управляемом коде есть 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[] SAFEARRAYBSTR

Маршалинг классов и структур

Другим аспектом маршалинга типов является способ передачи структуры в неуправляемый метод. Например, некоторые неуправляемые методы требуют указания структуры в качестве параметра. В таких случаях необходимо создать соответствующую структуру или соответствующий класс в управляемом коде и использовать в качестве параметра. Однако просто определение класса недостаточно, вам также нужно указать маршаллеру, как сопоставить поля в классе с неуправляемой структурой. Здесь пригодится атрибут 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;

Иногда маршалинг по умолчанию не решает задачу для вашей структуры. В статье по настройке структуры маршаллинга вы узнаете, как настроить способ маршаллирования структуры.