ネイティブ相互運用性のベスト プラクティス
.NET には、ネイティブ相互運用性コードをカスタマイズするさまざまな方法が用意されています。 この記事には、Microsoft の .NET チームがネイティブ相互運用性のために従うガイダンスが含まれています。
一般的なガイダンス
このセクションのガイダンスは、すべての相互運用シナリオに適用されます。
- ✔️ .NET 7 以降を対象とする場合は、可能であれば
[LibraryImport]
を使用してください。[DllImport]
を使用することが適切な場合があります。 ID が SYSLIB1054 のコード アナライザーは、そのような場合に通知します。
- ✔️ 実行: メソッドとパラメーターには、呼び出すネイティブ メソッドと同じ名前付けと大文字/小文字の設定を使用します。
- ✔️ 推奨: 定数値に対して同じ名前付けと大文字/小文字の設定を使用するようにします。
- ✔️ 実行: ネイティブ型に最も近い .NET 型を使用します。 たとえば、C# では、ネイティブ型が
unsigned int
の場合はuint
を使用します。 - ✔️ 実行: クラスではなく .NET 構造体を使った上位レベルのネイティブ型の表現を優先します。
- ✔️ C# でアンマネージド関数にコールバックを渡すときは、
Delegate
型ではなく関数ポインターを使用することをお勧めします。 - ✔️ DO では、配列パラメーターの
[In]
と[Out]
属性を使用します。 - ✔️ 目的の動作が既定の動作と異なる場合、DO は他の型でのみ
[In]
と[Out]
属性を使用します。 - ✔️ 推奨: System.Buffers.ArrayPool<T> を使用してネイティブ配列バッファーをプールするようにします。
- ✔️ 推奨: P/Invoke 宣言をネイティブ ライブラリと同じ名前と大文字/小文字の設定を使用してクラスにラップするようにします。
- これにより、
[LibraryImport]
属性または[DllImport]
属性で C#nameof
言語機能を使用してネイティブ ライブラリの名前を渡し、ネイティブ ライブラリの名前のスペルを間違えないようにすることができます。
- これにより、
- ✔️
SafeHandle
ハンドルを使用して、アンマネージド リソースをカプセル化するオブジェクトの有効期間を管理する。 詳細については、「アンマネージ リソースのクリーンアップ」を参照してください。 - ❌ アンマネージド リソースをカプセル化するオブジェクトの有効期間を管理するためのファイナライザーは回避する。 詳細については、「Dispose メソッドの実装」を参照してください。
LibraryImport 属性の設定
ID が SYSLIB1054 のコード アナライザーは、LibraryImportAttribute
をガイドするのに役立ちます。 ほとんどの場合、LibraryImportAttribute
を使用するには、既定の設定に頼るのではなく、明示的な宣言が必要です。 この設計は意図的なものであり、相互運用シナリオでの意図しない動作を回避するのに役立ちます。
dllImport 属性の設定
設定 | Default | 推奨事項 | 説明 |
---|---|---|---|
PreserveSig | true |
既定値を維持する | これが明示的に false に設定されている場合、失敗した HRESULT の戻り値は例外になります (そして結果として定義内の戻り値は null になります)。 |
SetLastError | false |
API によって異なります | API が GetLastError を使用し、Marshal.GetLastWin32Error を使用して値を取得する場合は、true に設定します。 API がエラーがあるという条件を設定している場合は、誤って上書きされないように他の呼び出しを行う前にエラーを取得します。 |
CharSet | コンパイラ定義 (文字セットのドキュメントで指定) | 定義内に文字列または文字が存在する場合は、明示的に CharSet.Unicode または CharSet.Ansi を使用します |
文字列のマーシャリング動作と false のときの ExactSpelling の動作を指定します。 Unix では CharSet.Ansi は実際には UTF8 である点に注意してください。 "ほとんど" の場合、Windows では Unicode が使用され、Unix では UTF8 が使用されます。 詳細については、文字セットのドキュメントを参照してください。 |
ExactSpelling | false |
true |
ランタイムで CharSet 設定の値に応じてサフィックスが "A" または "W" (CharSet.Ansi の場合は "A"、CharSet.Unicode の場合は "W") の代替の関数名が検索されないときに、これを true に設定し、わずかなパフォーマンス上のメリットを得ます。 |
文字列パラメーター
string
は、値 (ref
や out
ではない) で渡される場合、および次のいずれかによって渡される場合、ネイティブ コードによってピン留めされ、直接使用されます。
- LibraryImportAttribute.StringMarshalling は Utf16 と定義されます。
- 引数は明示的に
[MarshalAs(UnmanagedType.LPWSTR)]
としてマークされます。 - DllImportAttribute.CharSet が Unicodeです。
❌ [Out] string
パラメーターを使用しないでください。 文字列がインターン処理された文字列で、文字列パラメーターが [Out]
属性の値で渡された場合、ランタイムが不安定になる可能性があります。 文字列のインターン処理の詳細については、String.Intern のドキュメントを参照してください。
✔️ ネイティブ コードが文字バッファーを埋めると予想される場合は、ArrayPool
からの char[]
または byte[]
配列を検討してください。 このためには、引数を [Out]
として渡す必要があります。
DllImport 固有のガイダンス
✔️ ランタイムが予想される文字列エンコードを認識できるように、[DllImport]
内に CharSet
プロパティを設定することを検討してください。
✔️ StringBuilder
パラメーターを回避することを検討してください。 StringBuilder
のマーシャリングによって "StringBuilder
" ネイティブ バッファー コピーが作成されます。 そのため、非常に非能率的になる場合もあります。 その典型的なシナリオとして、文字列を受け取る Windows API を呼び出す場合があります。
- 目的の容量の
StringBuilder
を作成します (管理容量を割り当てます) {1} 。 - 次を呼び出します。
- ネイティブ バッファーを割り当てます {2} 。
[In]
"(StringBuilder
パラメーターの既定値)" の場合、内容をコピーします。[Out]
{3} "(StringBuilder
の既定値でもあります)" の場合、ネイティブ バッファーを新しく割り当てられたマネージド配列にコピーします。
ToString()
でさらに別のマネージド配列を割り当てます {4} 。
これは、ネイティブ コードから文字列を取得する {4} の割り当てです。 これを制限するために最適な方法は、StringBuilder
を別の呼び出しで再利用することですが、それでも 1 つの割り当てが節約されるだけです。 ArrayPool
から文字バッファーを使用してキャッシュする方がはるかにお勧めです。 以降の呼び出しでは ToString()
の割り当てのみで済むようになります。
StringBuilder
に関するもう 1 つの問題は、戻り値のバッファーが常に最初の null までコピーされることです。 渡された文字列が null で終了していない場合、または null 終端文字列が 2 つある場合、よくても P/Invoke は不正確になります。
StringBuilder
を "使用する" 場合、最後の問題は、相互運用のために常に考慮される非表示の null が容量に "含まれない" ことです。ます。 ほとんどの API はバッファー サイズに null が "含まれる" ことを想定しているため、これを誤りと考えられることがよくあります。 その結果、無駄な、または不要な割り当てが行われる可能性があります。 さらに、この問題により、ランタイムでコピーを最小化する StringBuilder
のマーシャリングを最適化できなくなります。
文字列のマーシャリングの詳細については、「文字列に対する既定のマーシャリング」と「Customizing string marshalling (文字列のマーシャリングのカスタマイズ)」を参照してください。
Windows 固有:
[Out]
文字列の場合、CLR は文字列を解放するために既定でCoTaskMemFree
を使用します。また、UnmanagedType.BSTR
とマークされている文字列の場合はSysStringFree
を使用します。 出力文字列バッファーがあるほとんどの API の場合: 渡される文字数には、常に null が含まれています。 返された値が、渡された文字数より少ない場合、呼び出しは成功し、値は末尾の null を "除いた" 文字数になります。 それ以外の場合、カウントは null 文字を "含む" バッファーの必要なサイズになります。
- 5 を渡し、4 を受け取る:文字列の長さは 4 文字であり、末尾に null が付きます。
- 5 を渡し、6 を受け取る:文字列の長さは 5 文字であり、null を保持するために 6 文字のバッファーが必要です。 Windows の文字列のデータ型
ブール型のパラメーターとフィールド
ブール値は混乱しやすいものです。 既定では、.NET の bool
は Windows の BOOL
にマーシャリングされます。これは 4 バイトの値です。 一方、C および C++ の _Bool
型と bool
型は "シングル" バイトです。 これは、戻り値の半分が破棄されると、結果のみが変わる "可能性" があるので、バグの追跡が困難になる可能性があります。 .NET の bool
値を C または C++ の bool
型にマーシャリングする方法の詳細については、bool
に関するドキュメントを参照してください。
GUID
GUID はシグネチャに直接使用できます。 多くの Windows API は、REFIID
のような GUID&
型のエイリアスを受け取ります。 メソッド シグネチャに参照パラメーターが含まれている場合は、GUID パラメーター宣言に ref
キーワードまたは [MarshalAs(UnmanagedType.LPStruct)]
属性を配置します。
GUID | 参照渡しの GUID |
---|---|
KNOWNFOLDERID |
REFKNOWNFOLDERID |
❌ ref
GUID パラメーター以外には [MarshalAs(UnmanagedType.LPStruct)]
を使用しないでください。
blittable 型
blittable 型は、マネージド コードとネイティブ コードで同じビット レベルの表現を持つ型です。 そのため、ネイティブ コードとの間でマーシャリングするために別の形式に変換する必要はなく、パフォーマンスが向上するため、推奨されます。 一部の型は blittable ではありませんが、blittable コンテンツを含んでいることがわかっています。 これらの型は、他の型に含まれていない場合は blittable 型と同様の最適化が行われますが、構造体のフィールド内、または UnmanagedCallersOnlyAttribute
の目的では blittable 型とは見なされません。
ランタイム マーシャリングが有効になっているときの blittable 型
blittable 型:
byte
、sbyte
、short
、ushort
、int
、uint
、long
、ulong
、single
、double
- インスタンス フィールドに対して blittable 値型のみを持つ固定レイアウトの構造体
- 固定レイアウトには
[StructLayout(LayoutKind.Sequential)]
または[StructLayout(LayoutKind.Explicit)]
が必要です - 構造体は既定で
LayoutKind.Sequential
です
- 固定レイアウトには
blittable コンテンツを含む型:
- blittable プリミティブ型の入れ子になっていない 1 次元配列 (
int[]
など) - インスタンス フィールドに対して blittable 値型のみを持つ固定レイアウトのクラス
- 固定レイアウトには
[StructLayout(LayoutKind.Sequential)]
または[StructLayout(LayoutKind.Explicit)]
が必要です - クラスは既定で
LayoutKind.Auto
です
- 固定レイアウトには
blittable ではない:
bool
blittable の場合がある:
char
場合によっては、blittable コンテンツを含む型:
string
in
、ref
、out
のいずれかを使用して参照によって blittable 型が渡された場合、または値によって blittable コンテンツを含む型が渡された場合、これらは中間バッファーにコピーされるのではなく、単にマーシャラーによってピン留めされます。
1 次元配列、"または" CharSet = CharSet.Unicode
が指定された [StructLayout]
で明示的にマークされている型の一部である場合、char
は blittable です。
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeCharStruct
{
public char c;
}
string
は 他の型に含まれておらず、引数として値 (ref
や out
ではない) で渡される場合、および次のいずれかで渡される場合、blittable コンテンツを含みます。
- StringMarshalling は Utf16 と定義されます。
- 引数は明示的に
[MarshalAs(UnmanagedType.LPWSTR)]
としてマークされます。 - CharSet は Unicode です。
固定された GCHandle
を作成しようとすると、型が blittable であるか、blittable コンテンツが含まれているかを確認できます。 型が文字列ではない場合、または blittable と見なされる場合、GCHandle.Alloc
は ArgumentException
をスローします。
ランタイム マーシャリングが無効になっているときの blittable 型
ランタイム マーシャリングが無効になっているときは、blittable である型の規則が大幅に簡単になります。 C# の unmanaged
型であり、[StructLayout(LayoutKind.Auto)]
でマークされたフィールドを持たない型は、すべて blittable です。 C# の unmanaged
型ではない型はすべて blittable ではありません。 配列や文字列など、内容が blittable である型の概念は、ランタイム マーシャリングが無効になっている場合は適用されません。 前述の規則で blittable と見なされない型は、ランタイム マーシャリングが無効になっているときはサポートされません。
これらの規則は、主に、bool
と char
が使われている状況において、組み込みシステムと異なります。 マーシャリングが無効になっている場合、bool
は 1 バイトの値として渡されて正規化されず、char
は常に 2 バイトの値として渡されます。 ランタイム マーシャリングが有効になっている場合、bool
は 1、2、または 4 バイトの値にマップすることができて常に正規化され、char
は CharSet
に応じて 1 または 2 バイトの値にマップされます。
✔️ 実行: 可能な限り、構造体を blittable にします。
詳細については次を参照してください:
マネージド オブジェクトのキープ アライブ
GC.KeepAlive()
で、KeepAlive メソッドがヒットするまでオブジェクトをスコープ内に維持することができます。
HandleRef
を使用すると、マーシャラーは P/Invoke の期間、オブジェクトの有効性を維持することができます。 メソッドのシグネチャで IntPtr
の代わりに使用できます。 事実上、SafeHandle
によってこのクラスは置き換えられるため、代わりに使用するようにします。
GCHandle
を使用すると、マネージド オブジェクトを固定し、そのオブジェクトへのネイティブ ポインターを取得できます。 次に基本的なパターンを示します。
GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();
固定は GCHandle
の既定ではありません。 もう 1 つの主なパターンは、ネイティブ コードを介してマネージド オブジェクトへの参照を渡し、(通常はコールバックを使用して) マネージド コードに戻すことです。 パターンを次に示します。
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 のデータ型
Windows API で一般的に使用されるデータ型と、Windows コードを呼び出すときに使用する C# 型の一覧を次に示します。
次の型は、32 ビット版と 64 ビット版の Windows でサイズは同じですが、名前は異なります。
幅 | 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
は、必ずしも同じサイズとは限りません。
C/C++ の long
型は、"少なくとも 32" ビットが含まれるように定義されています。 つまり、最低限必要なビット数があることを意味しますが、必要に応じて、プラットフォームにより多くのビットを使用することを選択できます。 次の表は、プラットフォーム間での C/C ++ long
データ型に提供されるビット数の違いを示しています。
プラットフォーム | 32 ビット | 64 ビット |
---|---|---|
Windows | 32 | 32 |
macOS/*nix | 32 | 64 |
これに対し、C# long
は常に 64 ビットです。 このため、C# long
を使用して C/C++ long
と相互運用しないようにすることをお勧めします。
(C/C++ long
に関するこの問題は、C/C++ char
、short
、int
、long long
には存在しません。これらすべてのプラットフォームがそれぞれ 8 ビット、16 ビット、32 ビット、64 ビットであるという理由からです。)
.NET 6 以降のバージョンでは、C/C++ の と unsigned long
のデータ型との相互運用に CLong
と long
CULong
の型を使用します。 次の例は 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);
}
構造体
マネージド構造体はスタックに対して作成され、メソッドから返されるまで削除されません。 定義上、これらは "固定" されます (GC によって移動されません)。 ネイティブ コードが、現在のメソッドの終わりを越えてポインターを使用しない場合は、アンセーフ コード ブロックで単にアドレスを取得することもできます。
blittable 型の構造体は、マーシャリング層から直接使用されるため、はるかに高パフォーマンスです。 できれば構造体を blittable 型にしてください (たとえば、bool
を避けます)。 詳細については、「Blittable Types (blittable 型)」セクションを参照してください。
構造体が blittable 型の "場合"、パフォーマンスを向上するために Marshal.SizeOf<MyStruct>()
ではなく sizeof()
を使用してください。 前述のように、固定された GCHandle
を作成しようとすることで、型が blittable であることを確認できます。 型が文字列ではない場合、または blittable と見なされる場合、GCHandle.Alloc
は ArgumentException
をスローします。
定義内の構造体へのポインターは、ref
で渡すか、unsafe
と *
を使用する必要があります。
✔️ 実行: マネージド構造体を、公式のプラットフォーム ドキュメントまたはヘッダーで使用されているシェイプと名前にできるだけ厳密に一致させます。
✔️ 実行: パフォーマンスを向上させるには、blittable 型の構造体に Marshal.SizeOf<MyStruct>()
ではなく C# の sizeof()
を使用します。
❌ 回避: クラスを使い、継承によって複雑なネイティブ型を表現しないようにしてください。
❌ 回避: 構造体の関数ポインター フィールドを表すために 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]
のような配列は、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;
...
}
ただし、固定バッファーに関する問題がいくつかあります。 blittable ではない型の固定バッファーは正しくマーシャリングされないので、インプレース配列は複数の個々のフィールドに展開する必要があります。 さらに、3.0 より前の .NET Framework および .NET Core では、固定バッファー フィールドを含む構造体が blittable 型ではない構造体内に入れ子にされている場合、固定バッファー フィールドはネイティブ コードに正しくマーシャリングされません。
.NET