方法: P/Invoke を使用して関数ポインターをマーシャリングする

.NET Framework P/Invoke 機能を使用してアンマネージ関数と相互運用する場合は、関数ポインターの代わりにマネージド デリゲートを使用できます。 ただし、可能であれば、代わりに C++ 相互運用機能を使用することをお勧めします。 P/Invoke はコンパイル時のエラー報告をほとんど提供せず、型セーフではなく、実装するのが面倒な場合があります。 アンマネージド API が DLL としてパッケージ化されていて、ソース コードが使用できない場合は、P/Invoke が唯一のオプションです。 それ以外の場合は、次の記事を参照してください。

関数ポインターを引数として受け取るアンマネージ API は、ネイティブ関数ポインターの代わりにマネージド デリゲートを使用してマネージド コードから呼び出すことができます。 コンパイラは、デリゲートを関数ポインターとしてアンマネージ関数に自動的にマーシャリングします。 必要なマネージド/アンマネージド遷移コードが挿入されます。

次のコードは、アンマネージド モジュールとマネージド モジュールで構成されています。 アンマネージ モジュールは、関数ポインターを受け取る呼び出しの TakesCallback 関数を定義する DLL です。 このアドレスは、関数の実行に使用されます。

// TraditionalDll5.cpp
// compile with: /LD /EHsc
#include <iostream>
#define TRADITIONALDLL_EXPORTS
#ifdef TRADITIONALDLL_EXPORTS
#define TRADITIONALDLL_API __declspec(dllexport)
#else
#define TRADITIONALDLL_API __declspec(dllimport)
#endif

extern "C" {
   /* Declare an unmanaged function type that takes two int arguments
      Note the use of __stdcall for compatibility with managed code */
   typedef int (__stdcall *CALLBACK)(int);
   TRADITIONALDLL_API int TakesCallback(CALLBACK fp, int);
}

int TakesCallback(CALLBACK fp, int n) {
   printf_s("[unmanaged] got callback address, calling it...\n");
   return fp(n);
}

マネージド モジュールは、ネイティブ コードにマーシャリングされるデリゲートを関数ポインターとして定義します。 この属性を DllImportAttribute 使用して、ネイティブ TakesCallback 関数をマネージド コードに公開します。 mainこの関数では、デリゲートのインスタンスが作成され、関数にTakesCallback渡されます。 プログラム出力は、この関数がネイティブ TakesCallback 関数によって実行されることを示しています。

マネージド関数では、ネイティブ関数の実行中に .NET Framework のガベージ コレクションによってデリゲートが再配置されるのを防ぐために、マネージド デリゲートのガベージ コレクションが抑制されます。

// MarshalDelegate.cpp
// compile with: /clr
using namespace System;
using namespace System::Runtime::InteropServices;

public delegate int GetTheAnswerDelegate(int);
public value struct TraditionalDLL {
   [DllImport("TraditionalDLL5.dll")]
   static public int TakesCallback(GetTheAnswerDelegate^ pfn, int n);
};

int GetNumber(int n) {
   Console::WriteLine("[managed] callback!");
   static int x = 0;
   ++x;
   return x + n;
}

int main() {
   GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
   pin_ptr<GetTheAnswerDelegate^> pp = &fp;
   Console::WriteLine("[managed] sending delegate as callback...");

   int answer = TraditionalDLL::TakesCallback(fp, 42);
}

DLL の一部は、従来 #include のディレクティブを使用してマネージド コードに公開されません。 実際、DLL は実行時にのみアクセスされるため、使用 DllImportAttribute してインポートされた関数の問題はコンパイル時に検出できません。

関連項目

C++ での明示的な P/Invoke の使用 (DllImport 属性)