次の方法で共有


ネイティブ相互運用性のベスト プラクティス

.NETでは、ネイティブ相互運用性コードをカスタマイズするためのさまざまな方法が提供されます。 この記事には、Microsoft の.NET チームがネイティブ相互運用性のために従うガイダンスが含まれています。

一般的なガイダンス

このセクションのガイダンスは、すべての相互運用シナリオに適用されます。

  • ✔️ .NET 7以降を対象とする場合は、可能であれば[LibraryImport]を使用してください。
    • を使用することが適切な場合があります。 ID が SYSLIB1054 のコード アナライザーは、そのような場合に通知します。
  • ✔️ 次の点に注意してください: メソッドとパラメーターには、呼び出すネイティブメソッドと同じ名前付けと大文字小文字を使用します。
  • ✔️ 推奨: 定数値に対して同じ名前付けと大文字/小文字の設定を使用するようにします。
  • ✔️ DO C 関数の引数に一致する P/Invoke および関数ポインターシグネチャを定義します。
  • ✔️ ネイティブ型に最も近い.NET型を使用してください。 たとえば、C# では、ネイティブ型が の場合は を使用します。
  • ✔️ クラスではなく、.NET構造体を使用して上位レベルのネイティブ型を表現することを好みます。
  • ✔️ C# でアンマネージ関数にコールバックを渡すときは、型ではなく、関数ポインターとを使用することを優先してください。 詳細については、を参照してください。
  • ✔️ DO では、配列パラメーターの と 属性を使用します。
  • ✔️ 目的の動作が既定の動作と異なる場合、DO は他の型でのみ と 属性を使用します。
  • ✔️ 推奨: を使用してネイティブ配列バッファーをプールするようにします。
  • ✔️ 推奨: P/Invoke 宣言をネイティブ ライブラリと同じ名前と大文字/小文字の設定を使用してクラスにラップするようにします。
    • これにより、 属性または 属性で C# 言語機能を使用してネイティブ ライブラリの名前を渡し、ネイティブ ライブラリの名前のスペルを間違えないようにすることができます。
  • ✔️ ハンドルを使用して、アンマネージド リソースをカプセル化するオブジェクトの有効期間を管理する。 詳細については、「アンマネージ リソースのクリーンアップ」を参照してください。
  • アンマネージド リソースをカプセル化するオブジェクトの有効期間を管理するためのファイナライザーは回避する。 詳細については、「Dispose メソッドの実装」を参照してください。

LibraryImport 属性の設定

ID が SYSLIB1054 のコード アナライザーは、 をガイドするのに役立ちます。 ほとんどの場合、 を使用するには、既定の設定に頼るのではなく、明示的な宣言が必要です。 この設計は意図的なものであり、相互運用シナリオでの意図しない動作を回避するのに役立ちます。

dllImport 属性の設定

設定 既定値 推奨事項 説明
PreserveSig true 既定値を維持する これが明示的に false に設定されている場合、失敗した HRESULT の戻り値は例外になります (そして結果として定義内の戻り値は null になります)。
SetLastError false API によって異なります API が GetLastError を使用し、Marshal.GetLastWin32Error を使用して値を取得する場合は、true に設定します。 API がエラーがあるという条件を設定している場合は、誤って上書きされないように他の呼び出しを行う前にエラーを取得します。
CharSet コンパイラ定義 (文字セットのドキュメントで指定) 定義内に文字列または文字が存在する場合は、明示的に または を使用します 文字列のマーシャリング動作と のときの の動作を指定します。 Unix では は実際には UTF8 である点に注意してください。 たいていの場合、WindowsではUnicodeを使用し、UnixではUTF8を使用します。 詳細については、文字セットのドキュメントを参照してください。
ExactSpelling false true ランタイムで 設定の値に応じてサフィックスが "A" または "W" ( の場合は "A"、 の場合は "W") の代替の関数名が検索されないときに、これを true に設定し、わずかなパフォーマンス上のメリットを得ます。

文字列パラメーター

は、値として渡される場合に( や ではなく)、かつ次のいずれかの条件で、ネイティブ コードによってピン留めされ、直接使用されます。

  • は と定義されます。
  • 引数は明示的に としてマークされます。
  • が です。

パラメーターを使用しないでください。 文字列がインターン処理された文字列で、文字列パラメーターが 属性の値で渡された場合、ランタイムが不安定になる可能性があります。 文字列のインターン処理の詳細については、 のドキュメントを参照してください。

✔️ ネイティブ コードが文字バッファーを埋めると予想される場合は、 からの または 配列を検討してください。 このためには、引数を として渡す必要があります。

DllImport 固有のガイダンス

✔️ ランタイムが予想される文字列エンコードを認識できるように、 内に プロパティを設定することを検討してください。

✔️ パラメーターを回避することを検討してください。 マーシャリングは、常にネイティブ バッファー コピーを作成します。 そのため、非常に非能率的になる場合もあります。 文字列を受け取るWindows API を呼び出す一般的なシナリオを次に示します。

  1. 目的の容量の を作成します (管理容量を割り当てます) 。
  2. 呼び出し:
    1. ネイティブ バッファーを割り当てます 。
    2. "( パラメーターの既定値)" の場合、内容をコピーします。
    3. "( の既定値でもあります)" の場合、ネイティブ バッファーを新しく割り当てられたマネージド配列にコピーします。
  3. でさらに別のマネージド配列を割り当てます 。

これは、ネイティブ コードから文字列を取得する の割り当てです。 これを制限するために最適な方法は、 を別の呼び出しで再利用することですが、それでも 1 つの割り当てが節約されるだけです。 から文字バッファーを使用してキャッシュする方がはるかにお勧めです。 以降の呼び出しでは の割り当てのみで済むようになります。

に関するもう 1 つの問題は、戻り値のバッファーが常に最初の null までコピーされることです。 渡された文字列が null で終了していない場合、または null 終端文字列が 2 つある場合、よくても P/Invoke は不正確になります。

を "" 場合、最後の問題は、相互運用のために常に考慮される非表示の null が容量に "含まれない" ことです。ます。 ほとんどの API はバッファー サイズに null が "含まれる" ことを想定しているため、これを誤りと考えられることがよくあります。 その結果、無駄な、または不要な割り当てが行われる可能性があります。 さらに、この問題により、ランタイムでコピーを最小化する のマーシャリングを最適化できなくなります。

文字列のマーシャリングの詳細については、「文字列に対する既定のマーシャリング」と「Customizing string marshalling (文字列のマーシャリングのカスタマイズ)」を参照してください。

Windows Specific[Out] 文字列の場合、CLR は既定で CoTaskMemFree を使用して文字列を解放するか、SysStringFree としてマークされている文字列に対して UnmanagedType.BSTR を使用します。 出力文字列バッファーがあるほとんどの API の場合: 渡される文字数には、常に null が含まれています。 返された値が、渡された文字数より少ない場合、呼び出しは成功し、値は末尾の null を "除いた" 文字数になります。 それ以外の場合、カウントはバッファーの必要なサイズにnull文字を含めたものになります。

  • 5 を渡し、4 を受け取る:文字列の長さは 4 文字であり、末尾に null が付きます。
  • 5 を渡し、6 を受け取る:文字列の長さは 5 文字であり、null を保持するために 6 文字のバッファーが必要です。 Windows 文字列のデータ型

ブール型のパラメーターとフィールド

ブール値は混乱しやすいものです。 既定では、.NET boolは 4 バイト値である Windows BOOL にマーシャリングされます。 一方、C および C++ の 型と 型は "シングル" バイトです。 これは、戻り値の半分が破棄されると、結果のみが変わる "可能性" があるので、バグの追跡が困難になる可能性があります。 C 型または C++ bool 型への.NET bool値のマーシャリングの詳細については、ブール型フィールドマーシャリングのカスタマイズに関するドキュメントを参照してください。

GUIDs

GUID はシグネチャに直接使用できます。 多くのWindows API は、GUID& などの REFIID 型のエイリアスを受け取ります。 メソッド シグネチャに参照パラメーターが含まれている場合は、GUID パラメーター宣言に キーワードまたは 属性を配置します。

GUID 参照渡しの GUID
KNOWNFOLDERID REFKNOWNFOLDERID

GUID パラメーター以外には を使用しないでください。

blittable 型

blittable 型は、マネージド コードとネイティブ コードで同じビット レベルの表現を持つ型です。 そのため、ネイティブ コードとの間でマーシャリングするために別の形式に変換する必要はなく、パフォーマンスが向上するため、推奨されます。 一部の型は blittable ではありませんが、blittable コンテンツを含んでいることがわかっています。 これらの型は、他の型に含まれていない場合は blittable 型と同様の最適化が行われますが、構造体のフィールド内、または の目的では blittable 型とは見なされません。

ランタイム マーシャリングが有効になっているときの blittable 型

Blittable 型:

  • 、、、、、、、、、
  • インスタンス フィールドに対して blittable 値型のみを持つ固定レイアウトの構造体
    • 固定レイアウトには または が必要です
    • 構造体は既定で です

blittable コンテンツを含む型:

  • blittable プリミティブ型の入れ子になっていない 1 次元配列 ( など)
  • インスタンス フィールドに対して blittable 値型のみを持つ固定レイアウトのクラス
    • 固定レイアウトには または が必要です
    • クラスは既定で です

blittable 以外:

  • bool

場合により blittable:

  • char

場合により、blittable コンテンツを含む型:

  • string

、、 のいずれかを使用して参照によって blittable 型が渡された場合、または値によって blittable コンテンツを含む型が渡された場合、これらは中間バッファーにコピーされるのではなく、単にマーシャラーによってピン留めされます。

は、1 次元配列または が指定された で明示的にマークされている型の一部である場合、blittable です。

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
public struct UnicodeCharStruct
{
    public char c;
}

が他の型に含まれていない場合、引数として値 ( または ではない) として渡されるとき、および次のいずれかの条件を満たす場合には、blittable コンテンツを含みます。

  • は と定義されます。
  • 引数は明示的に としてマークされます。
  • は Unicode です。

固定された を作成しようとすると、型が blittable であるか、blittable コンテンツが含まれているかを確認できます。 型が文字列ではない場合、または blittable と見なされる場合、 は をスローします。

ランタイム マーシャリングが無効になっているときの blittable 型

ランタイム マーシャリングが無効になっているときは、blittable である型の規則が大幅に簡単になります。 C# の 型であり、 でマークされたフィールドを持たない型は、すべて blittable です。 C# の 型ではない型はすべて blittable ではありません。 配列や文字列など、内容が blittable である型の概念は、ランタイム マーシャリングが無効になっている場合は適用されません。 前述の規則で blittable と見なされない型は、ランタイム マーシャリングが無効になっているときはサポートされません。

これらの規則は、主に、 と が使われている状況において、組み込みシステムと異なります。 マーシャリングが無効になっている場合、 は 1 バイトの値として渡されて正規化されず、 は常に 2 バイトの値として渡されます。 ランタイム マーシャリングが有効になっている場合、 は 1、2、または 4 バイトの値にマップすることができて常に正規化され、 は に応じて 1 または 2 バイトの値にマップされます。

✔️ 実行: 可能な限り、構造体を blittable にします。

詳細については次を参照してください:

  • Blittable 型と非 Blittable 型
  • 型のマーシャリング

マネージド オブジェクトのキープ アライブ

で、KeepAlive メソッドがヒットするまでオブジェクトをスコープ内に維持することができます。

を使用すると、マーシャラーは P/Invoke の期間、オブジェクトの有効性を維持することができます。 メソッドのシグネチャで の代わりに使用できます。 事実上、 によってこのクラスは置き換えられるため、代わりに使用するようにします。

を使用すると、マネージド オブジェクトを固定し、そのオブジェクトへのネイティブ ポインターを取得できます。 次に基本的なパターンを示します。

GCHandle handle = GCHandle.Alloc(obj, GCHandleType.Pinned);
IntPtr ptr = handle.AddrOfPinnedObject();
handle.Free();

固定は の既定ではありません。 もう 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();

メモリ リークを防ぐために、 を明示的に解放する必要があることを忘れないでください。

一般的なWindowsデータ型

Windows API で一般的に使用されるデータ型と、Windows コードを呼び出すときに使用する C# 型の一覧を次に示します。

次の型は、名前に関わらず、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 と を参照してください。
32 LONG32 int
32 CLONG uint と を参照してください。
32 DWORD uint と を参照してください。
32 DWORD32 uint
32 UINT uint
32 UINT32 uint
32 ULONG uint と を参照してください。
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

ポインターである次の型は、プラットフォームの幅に従います。 このような場合は を使用します。

符号付きのポインター型 ( を使用) 符号なしのポインター型 ( を使用)
HANDLE WPARAM
HWND UINT_PTR
HINSTANCE ULONG_PTR
LPARAM SIZE_T
LRESULT
LONG_PTR
INT_PTR

C PVOID である Windows void* は、IntPtr または UIntPtr としてマーシャリングできますが、可能な場合は void* を使用します。

Windows データ型

データ型の範囲

以前の組み込みサポート型

型の組み込みサポートが削除される珍しい場合があります。

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++

C/C++ と C# は、必ずしも同じサイズとは限りません。

C/C++ の 型は、"少なくとも 32" ビットが含まれるように定義されています。 つまり、最低限必要なビット数があることを意味しますが、必要に応じて、プラットフォームにより多くのビットを使用することを選択できます。 次の表は、プラットフォーム間での C/C ++ データ型に提供されるビット数の違いを示しています。

プラットフォーム 32 ビット 64 ビット
Windows 32 32
macOS/*nix 32 64

これに対し、C# は常に 64 ビットです。 このため、C# を使用して C/C++ と相互運用しないようにすることをお勧めします。

(C/C++ に関するこの問題は、C/C++ 、、、 には存在しません。これらすべてのプラットフォームがそれぞれ 8 ビット、16 ビット、32 ビット、64 ビットであるという理由からです。)

.NET 6 以降のバージョンでは、C/C++ CLong および CULong データ型との相互運用に、long 型と 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 型にしてください(例えば、 を避けてください)。 詳細については、「Blittable Types」セクションを参照してください。

構造体が blittable 型の "場合"、パフォーマンスを向上するために ではなく を使用してください。 前述のように、固定された を作成しようとすることで、型が blittable であることを確認できます。 型が文字列ではない場合、または blittable と見なされる場合、 は をスローします。

定義内の構造体へのポインターは、 で渡すか、 と を使用する必要があります。

✔️ 実行: マネージド構造体を、公式のプラットフォーム ドキュメントまたはヘッダーで使用されているシェイプと名前にできるだけ厳密に一致させます。

✔️ 実行: パフォーマンスを向上させるには、blittable 型の構造体に ではなく C# の を使用します。

❌ 明示的に文書化されていない限り、.NETランタイム ライブラリによって公開される構造体型の内部表現に依存しません。

クラスを用いて、継承によって複雑なネイティブ型を表現することは避けてください。

回避: 構造体の関数ポインター フィールドを表すために または フィールドを使用しないようにしてください。

と には必要なシグネチャがないため、渡されるデリゲートがネイティブ コードで想定されるシグネチャと一致するとは限りません。 さらに、.NET Framework と .NET Core では、ネイティブ表現からマネージド オブジェクトへのSystem.Delegateまたは System.MulticastDelegate を含む構造体をマーシャリングすると、ネイティブ表現のフィールドの値がマネージド デリゲートをラップする関数ポインターでない場合、ランタイムが不安定になります。 .NET 5 以降のバージョンでは、ネイティブ表現からマネージド オブジェクトへの System.Delegate または System.MulticastDelegate フィールドのマーシャリングはサポートされていません。 または ではなく、特定のデリゲート型を使用してください。

固定バッファー

のような配列は、2 つの フィールド、 と にマーシャリングする必要があります。 ネイティブ配列がプリミティブ型の場合、 キーワードを使用すると、もう少しわかりやすく記述できます。 たとえば、ネイティブ ヘッダーでは は次のようになります。

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 ではない型の固定バッファーは正しくマーシャリングされないので、インプレース配列は複数の個々のフィールドに展開する必要があります。 さらに、.NET Framework と 3.0 より前の .NET Core では、固定バッファー フィールドを含む構造体が blittable 以外の構造体内に入れ子になっている場合、固定バッファー フィールドはネイティブ コードに正しくマーシャリングされません。

P/Invoke エラーのトラブルシューティング

次の表は、一般的な症状を、考えられる原因と推奨される修正プログラムにマップします。

症状: 想定される原因 修正
DllNotFoundException 実行時にライブラリが見つかりません ライブラリ名、パス、プラットフォームを確認します。 を使用して読み込みをテストします。 Linux では、XまたはYを確認します。
EntryPointNotFoundException エクスポート名の不一致 ネイティブ エクスポートを検査します (Windows では dumpbin /exports、Linux では nm -D)。 C++ 名のマングリングをチェックする (閉じ括弧がありません)。 明示的に設定します。
AccessViolationException 署名の不一致、解放後の使用、またはピン留めの欠如 マネージド署名とネイティブ署名を比較します。 ネイティブ環境と比較して構造体のサイズを確認します。 メモリの有効期間を確認します。 blittable 署名を使用してマーシャリングの問題をトラブルシューティングする
サイレントデータ破損 型のサイズまたはエンコードが正しくありません 境界ログを追加します。 とネイティブ を比較します。 既知の入力/出力ペアを使用してテストします。
断続的なクラッシュ GC によって固定されていないオブジェクトが移動されたか、デリゲートが収集されました 完全な有効期間のルート コールバック デリゲート。 呼び出し間で保持されるポインターには、 または を使用します。
ヒープが無料で破損する 不適切なアロケーター アロケーターを一致させてください。決して、と、または、を混在させないでください。 ライブラリ独自の無料関数を使用します。

でデリゲートコレクションを禁止する

を使用してデリゲートを関数ポインターに変換する場合、ガベージ コレクターは、返されたポインターとソース デリゲートの関係を追跡しません。 ネイティブ コードがポインターの使用を終了する前にデリゲートがコレクションの対象になると、アプリケーションがクラッシュします。

を使用して収集を禁止します。

var callback = new MyDelegate((level, msgPtr) =>
{
    string msg = Marshal.PtrToStringUTF8(msgPtr) ?? string.Empty;
    Console.WriteLine($"[{level}] {msg}");
});

IntPtr fnPtr = Marshal.GetFunctionPointerForDelegate(callback);
NativeUsesCallback(fnPtr);
GC.KeepAlive(callback); // Prevent collection — fnPtr does not root the delegate

ネイティブ コードが呼び出しの後に関数ポインターを格納する場合 (たとえば、永続的なコールバックとして)、デリゲートは有効期間全体にわたってルート化する必要があります。通常は、 フィールドに格納します。

ドキュメントとネイティブ ヘッダーの間の競合を解決する

P/Invoke シグネチャを記述するときに、オンライン API ドキュメントと実際のネイティブ ヘッダー ファイルの間で不一致が発生する可能性があります。 ヘッダー ファイルは、関数シグネチャ、構造体レイアウト、型サイズ、および呼び出し規則の権限のあるソースです。 不明な場合は、ドキュメントのみに依存するのではなく、ヘッダーに対して P/Invoke 署名を確認します。