Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
.NET предоставляет различные способы настройки собственного кода взаимодействия. В этой статье приводятся рекомендации, которым следуют команды Microsoft .NET для обеспечения взаимодействия с нативными системами.
Общее руководство
Рекомендации в этом разделе относятся ко всем сценариям взаимодействия.
- ✔️ При возможности используйте
[LibraryImport]
, если возможно, при работе с .NET 7+.- Существуют случаи, когда используется
[DllImport]
соответствующим образом. Анализатор кода с идентификатором SYSLIB1054 сообщает вам, когда это так.
- Существуют случаи, когда используется
- ✔️ РЕКОМЕНДУЕТСЯ использовать такое же именование и регистр для методов и параметров, как и для нативного метода, который вы хотите вызвать.
- ✔️ РАССМОТРИТЕ использование такого же именования и написания для константных значений.
- ✔️ РЕКОМЕНДУЕТСЯ использовать типы .NET, которые наиболее сопоставимы с нативным типом. Например, если собственный тип в C# —
uint
, используйтеunsigned int
. - ✔️ Предпочитайте выражение собственных типов более высокого уровня с помощью структур .NET, а не классов.
- ✔️ Рекомендуется использовать указатели функций, а не
Delegate
типы при передаче обратных вызовов в неуправляемые функции в C#. - ✔️ Обязательно используйте атрибуты
[In]
и[Out]
для параметров массива. - ✔️ Используйте
[In]
и[Out]
атрибуты только для других типов, если нужное поведение отличается от поведения по умолчанию. - ✔️ Подумайте о использовании System.Buffers.ArrayPool<T> для объединения нативных буферов массивов.
- ✔️ ДОПУСТИМО создавать оболочку для объявлений P/Invoke в классе с таким же именем и регистром, как в нативной библиотеке.
- Это позволяет
[LibraryImport]
[DllImport]
использовать функцию языка C#nameof
, чтобы передать имя собственной библиотеки и убедиться, что вы не пропустили имя собственной библиотеки.
- Это позволяет
- ✔️ Используйте
SafeHandle
дескриптор для управления временем существования объектов, которые инкапсулируют неуправляемые ресурсы. Дополнительные сведения см. в разделе Очистка неуправляемых ресурсов. - ❌ Избегайте завершения для управления временем существования объектов, которые инкапсулируют неуправляемые ресурсы. Дополнительные сведения см. в разделе «Реализация метода Dispose».
Параметры атрибута LibraryImport
Анализатор кода с идентификатором SYSLIB1054 помогает вам LibraryImportAttribute
. В большинстве случаев использование LibraryImportAttribute
требует явного объявления, а не использования параметров по умолчанию. Эта конструкция является преднамеренной и помогает избежать непреднамеренного поведения в сценариях взаимодействия.
Параметры атрибута DllImport
Настройка | По умолчанию. | Рекомендация | Сведения |
---|---|---|---|
PreserveSig | true |
Сохранить значение по умолчанию | Если явно задано значение false, неудачные возвращаемые значения HRESULT будут преобразованы в исключения (при этом возвращаемое значение в определении становится null). |
SetLastError | false |
Зависит от API | Присвойте этому параметру значение true, если для получения значения в API используется GetLastError и Marshal.GetLastWin32Error. Если API устанавливает условие, указывающее на ошибку, перед тем как выполнить другие вызовы, получите информацию об ошибке, чтобы избежать ее непреднамеренной перезаписи. |
CharSet | Определяемый компилятором (указанный в документации по charset) | Если в определении есть строки или символы, используйте CharSet.Unicode или CharSet.Ansi в явном виде |
Указывает специфику обработки строк и что делает ExactSpelling , когда false . Обратите внимание, что в Unix CharSet.Ansi имеет кодировку UTF8.
Обычно в Windows используется Юникод, а в Unix — UTF8. См. дополнительные сведения в документации по кодировке. |
ExactSpelling | false |
true |
Если присвоить этому параметру значение true, можно немного повысить производительность — среда выполнения не будет искать другие имена функций с суффиксом "A" или "W" в зависимости от значения параметра CharSet ("A" для CharSet.Ansi и "W" для CharSet.Unicode ). |
Параметры строки
Объект string
закрепляется и используется непосредственно машинным кодом (а не копируется), когда он передается по значению (не ref
или out
) и соответствует любому из следующих условий:
- LibraryImportAttribute.StringMarshalling определяется как Utf16.
- Аргумент явно помечен как
[MarshalAs(UnmanagedType.LPWSTR)]
. - DllImportAttribute.CharSet имеет значение Unicode.
❌ Не используйте [Out] string
параметры. Строковые параметры, передаваемые по значению с атрибутом [Out]
, могут дестабилизировать среду выполнения, если строка является интернированной. См. дополнительные сведения в документации по методу интернирования строк String.Intern.
✔️ Рассмотрите char[]
или byte[]
массивы из ArrayPool
, когда ожидается, что нативный код заполнит буфер символов. Для этого требуется передать аргумент как [Out]
.
Руководство, cпецифичное для DllImport
✔️ Рассмотрите возможность настройки свойства CharSet
в [DllImport]
так, чтобы среда выполнения знала ожидаемое кодирование строк.
✔️ Следует избегать параметров StringBuilder
. При маршаллингe StringBuilder
всегда создается копия нативного буфера. Таким образом, это может быть крайне неэффективным. Выполните обычный сценарий вызова API Windows, который принимает строку:
- Создайте нужную
StringBuilder
емкость (выделяет управляемую емкость). {1} - Взывать:
- Выделяет собственный буфер {2}.
- Копирует содержимое, если
[In]
(по умолчанию дляStringBuilder
параметра) - Копирует собственный буфер в только что выделенный управляемый массив, если
[Out]
{3}(также по умолчанию дляStringBuilder
).
-
ToString()
выделяет еще один управляемый массив {4}.
{4} Это выделение для получения строки из машинного кода. Лучшее, что можно сделать, чтобы ограничить потери — это повторно использовать StringBuilder
в другом вызове, но это по-прежнему экономит только одно выделение. Гораздо лучше использовать и кэшировать буфер символов из ArrayPool
. Затем можно перейти к выделению только для ToString()
во время последующих вызовов.
Еще одна проблема, связанная с StringBuilder
, заключается в том, что этот атрибут всегда создает резервную копию буфера возврата к первому значению NULL. Если возвращенная строка не завершена или завершается двумя символами NULL, атрибут P/Invoke задан неправильно (в лучшем случае).
Если вы все же используетеStringBuilder
, возникает еще одна проблема — емкость не включает скрытого значения NULL, которое всегда учитывается при взаимодействии. Пользователи часто ошибаются, так как для многих API требуется, чтобы в размер буфера включалось значение NULL. Это может привести к чрезмерному или ненужному распределению. Кроме того, эта ошибка не позволяет среде выполнения оптимизировать маршалинг StringBuilder
для минимизации количества копий.
Дополнительные сведения см. в статье Маршалинг по умолчанию для строк и разделе Customizing string parameters (Настройка строковых параметров).
Для среды Windows. Для строк
[Out]
в среде CLR по умолчанию используетсяCoTaskMemFree
(для свободных строк) илиSysStringFree
(для строк, обозначенных какUnmanagedType.BSTR
). Для большинства API с буфером выходной строки: число переданных символов должно включать нулевой символ. Если возвращаемое значение меньше, чем количество переданных символов, значит, вызов завершился успешно. В таком случае значение — это количество символов без NULL в конце. В противном случае значение - требуемый размер буфера, включая нулевой символ.
- Передайте 5, получите 4: строка длиной 4 символа с завершающим нулевым символом.
- Передайте 5, получите 6: строка имеет длину 5 символов, требуется 6 символьных буфера, чтобы сохранить значение NULL. Windows Data Types for Strings (Типы данных Windows для работы со строками)
Логические параметры и поля
Булевые значения легко напутать. По умолчанию, тип .NET bool
маршалируется в тип Windows BOOL
, где он представлен как значение из 4 байт. Но типы _Bool
и bool
в C и C++ имеют размер один байт. Это может привести к сложностям в отслеживании ошибок, поскольку половина возвращаемого значения будет отброшена, что лишь возможно изменит результат. Для получения дополнительной информации об управлении значениями .NET bool
для типов C или C++ bool
, см. документацию по настройке маршалинга булевых полей.
Идентификаторы GUID
Идентификаторы GUID можно использовать непосредственно в сигнатурах. Многие API Windows принимают такие псевдонимы типа GUID&
, как REFIID
. Если сигнатура метода содержит ссылочный параметр, в объявление параметра GUID поместите ключевое слово ref
или атрибут [MarshalAs(UnmanagedType.LPStruct)]
.
GUID | Идентификатор GUID для передачи по ссылке |
---|---|
KNOWNFOLDERID |
REFKNOWNFOLDERID |
❌ Не используйте [MarshalAs(UnmanagedType.LPStruct)]
ничего, кроме ref
параметров GUID.
Плоские типы
Непреобразуемые типы — это типы данных с одинаковым представлением на битовом уровне в управляемом и машинном коде. Для маршалинга в нативный код и из него эти типы не нужно преобразовывать в другой формат, что увеличивает производительность. Поэтому им следует отдавать предпочтение. Некоторые типы не являются blittable, но, как известно, содержат переносимое содержимое. Эти типы имеют аналогичные оптимизации, как блиттбл-типы, когда они не содержатся в другом типе, но не считаются блиттбл в полях структур или для целей UnmanagedCallersOnlyAttribute
.
Типы blittable при включенном маршалинге среды выполнения
Блитабельные типы данных:
-
byte
,sbyte
,short
,ushort
,int
,uint
,long
,ulong
,single
,double
- структуры с фиксированным макетом, которые имеют только типы значений, допускающих перерезку для полей экземпляра
- Для фиксированного макета требуется указать
[StructLayout(LayoutKind.Sequential)]
или[StructLayout(LayoutKind.Explicit)]
- Структуры являются по
LayoutKind.Sequential
умолчанию
- Для фиксированного макета требуется указать
Типы с содержимым, которое можно напрямую обменивать:
- невложенные одномерные массивы блитабельных примитивных типов (например,
int[]
) - классы с фиксированным макетом, которые имеют только типы значений, поддерживающие преобразование в память, для полей экземпляра.
- Для фиксированной структуры требуется указать
[StructLayout(LayoutKind.Sequential)]
или[StructLayout(LayoutKind.Explicit)]
. - Классы
LayoutKind.Auto
включены по умолчанию
- Для фиксированной структуры требуется указать
Преобразуемые типы данных:
bool
ПЕРИОДИЧЕСКИ преобразуемые типы данных:
char
Типы, содержимое которых иногда является blittable:
string
При передаче блиттабельных типов по ссылке с помощью in
, ref
или out
, или при передаче типов с блиттабельным содержимым по значению, они просто закрепляются маршаллером, а не копируются в промежуточный буфер.
Тип char
является блиттабельным в одномерном массиве или, если он является частью типа, который явно помечен [StructLayout]
с CharSet = CharSet.Unicode
.
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeCharStruct
{
public char c;
}
string
содержит блиттовое содержимое, если он не содержится в другом типе и передается значением (не ref
или out
) в качестве аргумента и при одном из следующих условий:
- StringMarshalling определяется как Utf16.
- Аргумент явно помечен как
[MarshalAs(UnmanagedType.LPWSTR)]
. - CharSet — Юникод.
Вы можете узнать, является ли тип перерезаемым или содержит закрепления содержимое, пытаясь создать закрепленное GCHandle
содержимое. Если тип не является строкой или считается непреобразуемым, GCHandle.Alloc
вызовет ArgumentException
.
Типы blittable при отключении маршалинг среды выполнения
При отключении маршаллинга среды выполнения правила, для которых типы являются перерезаемыми, значительно проще. Все типы C# unmanaged
и не имеют полей, помеченных как [StructLayout(LayoutKind.Auto)]
blittable. Все типы, которые не являются типами C# unmanaged
, не являются перерезаемыми. Концепция типов с объемным содержимым, например массивами или строками, не применяется при отключении маршаллинга среды выполнения. Любой тип, который не считается перерезанным в приведенном выше правиле, не поддерживается при отключении маршаллинга среды выполнения.
Эти правила отличаются от встроенной системы в первую очередь в ситуациях, когда bool
и char
используются. При отключении маршаллинга bool
передается как 1-байтовое значение, не нормализовано, а char
всегда передается в виде 2-байтового значения. Если маршалирование среды выполнения включено, bool
может сопоставляться со значением 1, 2 или 4-байтов и всегда нормализовано, а char
также сопоставляется со значением 1 или 2 байтов в зависимости от значения CharSet
.
✔️ РЕКОМЕНДУЕТСЯ по возможности сделать свои структуры данных непреобразуемыми.
Дополнительные сведения см. в разделе:
Сохранение управляемых объектов активными
GC.KeepAlive()
гарантирует, что объект остается в области видимости, пока не вызовется метод KeepAlive.
HandleRef
позволяет маршалеру сохранять объект активным во время выполнения P/Invoke. Его можно использовать вместо IntPtr
в сигнатурах метода.
SafeHandle
фактически заменяет этот класс и должен использоваться вместо него.
GCHandle
разрешает закреплять управляемый объект и получать нативный указатель на него. Базовый шаблон приведен ниже:
GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();
Закрепление по умолчанию не предусмотрено для GCHandle
. Еще один основной шаблон предназначен для передачи ссылки на управляемый объект через машинный код и обратно на управляемый код, обычно с обратным вызовом. Вот этот шаблон:
GCHandle handle = GCHandle.Alloc(obj);
SomeNativeEnumerator(callbackDelegate, GCHandle.ToIntPtr(handle));
// In the callback
GCHandle handle = GCHandle.FromIntPtr(param);
object managedObject = handle.Target;
// After the last callback
handle.Free();
Не забывайте, что GCHandle
необходимо явно освобождать, чтобы избежать утечек памяти.
Общие типы данных Windows
Ниже приведен список типов данных, часто используемых в API Windows, и типов данных C#, используемых при вызове в виде кода Windows.
Указанные ниже типы имеют одинаковый размер в 32-разрядной и 64-разрядной версиях Windows независимо от их имен.
Width | Windows | C# | Альтернатива |
---|---|---|---|
32 | BOOL |
int |
bool |
8 | BOOLEAN |
byte |
[MarshalAs(UnmanagedType.U1)] bool |
8 | BYTE |
byte |
|
8 | UCHAR |
byte |
|
8 | UINT8 |
byte |
|
8 | CCHAR |
byte |
|
8 | CHAR |
sbyte |
|
8 | CHAR |
sbyte |
|
8 | INT8 |
sbyte |
|
16 | CSHORT |
short |
|
16 | INT16 |
short |
|
16 | SHORT |
short |
|
16 | ATOM |
ushort |
|
16 | UINT16 |
ushort |
|
16 | USHORT |
ushort |
|
16 | WORD |
ushort |
|
32 | INT |
int |
|
32 | INT32 |
int |
|
32 | LONG |
int |
Ознакомьтесь с разделами CLong и CULong . |
32 | LONG32 |
int |
|
32 | CLONG |
uint |
Ознакомьтесь с разделами CLong и CULong . |
32 | DWORD |
uint |
Ознакомьтесь с разделами CLong и CULong . |
32 | DWORD32 |
uint |
|
32 | UINT |
uint |
|
32 | UINT32 |
uint |
|
32 | ULONG |
uint |
Ознакомьтесь с разделами CLong и CULong . |
32 | ULONG32 |
uint |
|
64 | INT64 |
long |
|
64 | LARGE_INTEGER |
long |
|
64 | LONG64 |
long |
|
64 | LONGLONG |
long |
|
64 | QWORD |
long |
|
64 | DWORD64 |
ulong |
|
64 | UINT64 |
ulong |
|
64 | ULONG64 |
ulong |
|
64 | ULONGLONG |
ulong |
|
64 | ULARGE_INTEGER |
ulong |
|
32 | HRESULT |
int |
|
32 | NTSTATUS |
int |
Следующие типы являются указателями и зависят от ширины платформы. Для них используйте IntPtr
/UIntPtr
.
Типы указателей со знаком (используйте IntPtr ) |
Типы указателей без знака (используйте UIntPtr ) |
---|---|
HANDLE |
WPARAM |
HWND |
UINT_PTR |
HINSTANCE |
ULONG_PTR |
LPARAM |
SIZE_T |
LRESULT |
|
LONG_PTR |
|
INT_PTR |
Windows PVOID
, который является C void*
, может быть маршалирован как IntPtr
или UIntPtr
, но предпочтительнее использовать void*
, когда это возможно.
Ранее поддерживаемые встроенные типы
Редкие случаи, когда встроенная поддержка типа удаляется.
Поддержка встроенного маршала UnmanagedType.HString
и встроенного маршала UnmanagedType.IInspectable
была удалена в выпуске .NET 5. Необходимо повторно компилировать двоичные файлы, которые используют этот тип маршалинга и предназначены для предыдущего фреймворка. Этот тип по-прежнему можно маршалировать, но его необходимо маршалировать вручную, как показано в следующем примере кода. Этот код будет работать вперед и также совместим с предыдущими платформами.
public sealed class HStringMarshaler : ICustomMarshaler
{
public static readonly HStringMarshaler Instance = new HStringMarshaler();
public static ICustomMarshaler GetInstance(string _) => Instance;
public void CleanUpManagedData(object ManagedObj) { }
public void CleanUpNativeData(IntPtr pNativeData)
{
if (pNativeData != IntPtr.Zero)
{
Marshal.ThrowExceptionForHR(WindowsDeleteString(pNativeData));
}
}
public int GetNativeDataSize() => -1;
public IntPtr MarshalManagedToNative(object ManagedObj)
{
if (ManagedObj is null)
return IntPtr.Zero;
var str = (string)ManagedObj;
Marshal.ThrowExceptionForHR(WindowsCreateString(str, str.Length, out var ptr));
return ptr;
}
public object MarshalNativeToManaged(IntPtr pNativeData)
{
if (pNativeData == IntPtr.Zero)
return null;
var ptr = WindowsGetStringRawBuffer(pNativeData, out var length);
if (ptr == IntPtr.Zero)
return null;
if (length == 0)
return string.Empty;
return Marshal.PtrToStringUni(ptr, length);
}
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern int WindowsCreateString([MarshalAs(UnmanagedType.LPWStr)] string sourceString, int length, out IntPtr hstring);
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern int WindowsDeleteString(IntPtr hstring);
[DllImport("api-ms-win-core-winrt-string-l1-1-0.dll")]
[DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
private static extern IntPtr WindowsGetStringRawBuffer(IntPtr hstring, out int length);
}
// Example usage:
[DllImport("api-ms-win-core-winrt-l1-1-0.dll", PreserveSig = true)]
internal static extern int RoGetActivationFactory(
/*[MarshalAs(UnmanagedType.HString)]*/[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(HStringMarshaler))] string activatableClassId,
[In] ref Guid iid,
[Out, MarshalAs(UnmanagedType.IUnknown)] out object factory);
Рекомендации по кроссплатформенным типам данных
Существуют типы на языке C/C++, которые имеют широту в том, как они определены. При написании кроссплатформенного взаимодействия могут возникать случаи, когда платформы отличаются и могут вызывать проблемы, если они не рассматриваются.
C/C++ long
C/C++ long
и C# long
не обязательно одинакового размера.
Тип long
в C/C++ определяется как "не менее 32" битов. Это означает, что существует минимальное количество необходимых битов, но платформы могут использовать больше битов при желании. В следующей таблице показаны различия в предоставленных битах для типа данных C/C++ long
между платформами.
Платформа | 32-разрядное | 64-разрядное |
---|---|---|
Windows | 32 | 32 |
macOS/*nix | 32 | 64 |
В отличие от этого, C# long
всегда имеет 64-разрядную версию. По этой причине рекомендуется избежать использования C# long
для взаимодействия с C/C++ long
.
(Эта проблема с C/C++ не существует для C/C++ long
char
, short
int
и long long
так как они имеют 8, 16, 32 и 64 бит соответственно на всех этих платформах.)
В .NET 6 и более поздних версиях используйте типы CLong
и CULong
для взаимодействия с типами данных C/C++ long
и unsigned long
. Следующий пример предназначен для CLong
, но можно использовать CULong
для абстрагирования unsigned long
таким же образом.
// Cross platform C function
// long Function(long a);
[DllImport("NativeLib")]
extern static CLong Function(CLong a);
// Usage
nint result = Function(new CLong(10)).Value;
При выборе .NET 5 и более ранних версий следует объявить отдельные подписи Windows и не Windows, чтобы справиться с этой проблемой.
static readonly bool IsWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
// Cross platform C function
// long Function(long a);
[DllImport("NativeLib", EntryPoint = "Function")]
extern static int FunctionWindows(int a);
[DllImport("NativeLib", EntryPoint = "Function")]
extern static nint FunctionUnix(nint a);
// Usage
nint result;
if (IsWindows)
{
result = FunctionWindows(10);
}
else
{
result = FunctionUnix(10);
}
Структуры
Управляемые структуры создаются в стеке и не удаляются, пока метод не вернет значение. Таким образом, по определению, эти структуры "закреплены" (не перемещаются службой сборки мусора). Вы также можете просто взять адрес в блоках неуправляемого кода, если родной код не будет использовать указатель после завершения текущего метода.
Блитблочные структуры более производительны, так как их можно использовать непосредственно на маршалинговом уровне. Попробуйте сделать структуры непреобразуемыми (например, не используйте атрибут bool
). См. дополнительные сведения в разделе Непреобразуемые типы данных.
Если структура является непреобразуемой, используйте атрибут sizeof()
вместо Marshal.SizeOf<MyStruct>()
, чтобы повысить производительность. Как описано выше, чтобы проверить, является ли тип непреобразуемым, попытайтесь создать закрепленный атрибут GCHandle
. Если тип не является строкой или считается непреобразуемым, атрибут GCHandle.Alloc
вызовет ArgumentException
.
Указатели на структуры в определениях необходимо передавать либо через ref
, либо использовать unsafe
и *
.
✔️ НЕОБХОДИМО сопоставить управляемую структуру как можно точнее с формой и именами, которые используются в официальной документации платформы или заголовках.
✔️ РЕКОМЕНДУЕТСЯ использовать C# sizeof()
вместо Marshal.SizeOf<MyStruct>()
для непреобразуемых структур, чтобы повысить производительность.
❌ Не полагайтесь на внутреннее представление типов структур, предоставляемых библиотеками среды выполнения .NET, если только они явно не задокументированы.
❌ Избегайте использования классов для выражения сложных собственных типов с помощью наследования.
❌ ИЗБЕГАЙТЕ использования полей System.Delegate
или System.MulticastDelegate
для представления полей с указателями функций в структурах.
Поскольку System.Delegate и System.MulticastDelegate не имеют требуемых сигнатур, они не гарантируют соответствие переданного делегата сигнатуре, которую ожидает код на уровне системы. Кроме того, в платформах .NET Framework и .NET Core маршалирование структуры, содержащей System.Delegate
или System.MulticastDelegate
, из собственного представления в управляемый объект может дестабилизировать среду выполнения, если значение поля в собственном представлении не является указателем функции, который упаковывает управляемый делегат. В .NET 5 и более поздних версиях маршалирование поля System.Delegate
или System.MulticastDelegate
из собственного представления в управляемый объект не поддерживается. Используйте вместо System.Delegate
или System.MulticastDelegate
конкретный тип делегата.
Фиксированные буферы
Массив, как INT_PTR Reserved1[2]
, должен быть маршалирован в два поля IntPtr
, Reserved1a
и Reserved1b
. Если собственный массив имеет простой тип, используйте ключевое слово fixed
, чтобы записать его более точно. Например, SYSTEM_PROCESS_INFORMATION
в исходном заголовке выглядит следующим образом:
typedef struct _SYSTEM_PROCESS_INFORMATION {
ULONG NextEntryOffset;
ULONG NumberOfThreads;
BYTE Reserved1[48];
UNICODE_STRING ImageName;
...
} SYSTEM_PROCESS_INFORMATION;
В C# можно написать такой код:
internal unsafe struct SYSTEM_PROCESS_INFORMATION
{
internal uint NextEntryOffset;
internal uint NumberOfThreads;
private fixed byte Reserved1[48];
internal Interop.UNICODE_STRING ImageName;
...
}
Но с буферами фиксированного размера возникают некоторые ошибки. Буферы неподлежащих прямой проекции типов не маршалируются правильно, поэтому локальный массив следует развернуть в несколько отдельных полей. Кроме того, в .NET Framework и .NET Core версий, предшествующих 3.0, если структура содержит поле буфера фиксированного размера и вложена в преобразуемую структуру, поле буфера фиксированного размера не маршалируется правильно в машинный код.