Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
Маршаллирование — это процесс преобразования типов, когда они должны пересекать управляемый и машинный код.
Маршаллирование необходимо, так как типы в управляемом и неуправляемом коде отличаются. Например, управляемый код содержит string, а неуправляемые строки могут иметь кодировку .NET string (UTF-16), кодировку кодовой страницы ANSI, UTF-8, с нулевым окончанием, ASCII и т. д. По умолчанию подсистема P/Invoke пытается выполнить корректные действия на основе поведения по умолчанию, описанного в этой статье. Однако для таких ситуаций, когда вам нужен дополнительный контроль, можно использовать атрибут MarshalAs , чтобы указать ожидаемый тип на неуправляемой стороне. Например, если вы хотите, чтобы строка была отправлена в виде строки, завершаемой значением NULL, UTF-8, это можно сделать следующим образом:
[LibraryImport("somenativelibrary.dll")]
static extern int MethodA([MarshalAs(UnmanagedType.LPUTF8Str)] 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, которая использует long, используйте CLong или CULong (.NET 6+) вместо C# long. См. статью Соображения о типах данных для разных платформ для получения дополнительных сведений и обходных решений для более ранних версий .NET.
Замечание
Тип wchar_t — UTF-16 (2 байта) на Windows, но определяется компилятором на других платформах , как правило, UTF-32 (4 байт) в Linux и macOS. Из-за этого wchar_t* трудно использовать как единый кроссплатформенный ABI. При разработке кроссплатформенного собственного API предпочтительно использовать char* с четко определённым соглашением о кодировке (например, UTF-8) вместо wchar_t*.
Замечание
Собственные char* строки используют кодировку, определяемую библиотекой или платформой. При вызове функции C, которая принимает char*, обеспечьте соответствие ожидаемой кодировке, выбрав правильный параметр маршаллинга строк, например StringMarshalling.Utf8 для UTF-8, StringMarshalling.Utf16 для UTF-16 или StringMarshalling.Custom для других кодировок.
| Ключевое слово 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 Здесь атрибут становится полезным.
using System;
using System.Runtime.InteropServices;
Win32Interop.GetSystemTime(out Win32Interop.SystemTime systemTime);
Console.WriteLine(systemTime.Year);
internal static partial class Win32Interop
{
[LibraryImport("kernel32.dll")]
internal static partial void GetSystemTime(out SystemTime systemTime);
[StructLayout(LayoutKind.Sequential)]
internal ref 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;
}
}
В предыдущем коде показан простой пример вызова функции GetSystemTime() . Интересный бит находится в строке 13. Атрибут указывает, что поля класса должны быть сопоставлены последовательно с структурой на другой (неуправляемой) стороне. Это означает, что именование полей не важно, важно только их порядок, так как он должен соответствовать неуправляемой структуре, показанной в следующем примере:
typedef struct _SYSTEMTIME {
WORD wYear;
WORD wMonth;
WORD wDayOfWeek;
WORD wDay;
WORD wHour;
WORD wMinute;
WORD wSecond;
WORD wMilliseconds;
} SYSTEMTIME, *PSYSTEMTIME, *LPSYSTEMTIME;
Иногда маршалирование по умолчанию для вашей структуры не соответствует вашим требованиям. Статья Настройка маршаллинга структур научит вас настраивать процесс маршаллирования вашей структуры.