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


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

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

Маршаллирование необходимо, так как типы в управляемом и неуправляемом коде отличаются. Например, в управляемом коде имеется string, тогда как неуправляемые строки могут иметь кодировку .NET string (UTF-16), кодировку ANSI Code Page, 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 предоставляются нативному коду при применении этого атрибута, см. в разделе disabled runtime marshalling.

Правила по умолчанию для маршалинга распространенных типов

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

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