方法: 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 を使用してインポートされた関数の問題はコンパイル時に検出できません。