方法: PInvoke を使用して関数ポインターをマーシャリングする
このトピックでは、.NET Framework の P/Invoke 機能を使用してアンマネージ関数と相互運用するときに、関数ポインターの代わりにマネージ デリゲートを使用する方法について説明します。 ただし、P/Invoke は、ほとんどコンパイル時のエラーを報告せず、タイプセーフでもなく、また実装に時間がかかるため、可能な場合、Visual C++ プログラマは P/Invoke の代わりに C++ Interop を使用することが推奨されています。 アンマネージ API が DLL としてパッケージ化されていて、そのソース コードが利用できない場合、P/Invoke を使用する以外方法はありません。 それ以外の場合、次のトピックを参照してください。
関数ポインターを引数として受け取るアンマネージ API は、ネイティブ関数ポインターの代わりにマネージ デリゲートを使用してマネージ コードから呼び出すことができます。 コンパイラは、デリゲートを関数ポインターとしてアンマネージ関数に自動的にマーシャリングし、必要なマネージまたはアンマネージ遷移コードを挿入します。
使用例
次のコードは、アンマネージ モジュールとマネージ モジュールで構成されます。 アンマネージ モジュールは、関数ポインターを受け取る TakesCallback と呼ばれる関数を定義する DLL です。 このアドレスは、関数を実行するために使用されます。
マネージ モジュールは、関数ポインターとしてネイティブ コードにマーシャリングされるデリゲートを定義し、DllImportAttribute 属性を使用して、ネイティブ TakesCallback 関数をマネージ コードに公開します。 main 関数では、デリゲートのインスタンスが作成され、TakesCallback 関数に渡されます。 プログラムの出力には、この関数がネイティブ TakesCallback 関数によって実行されたことが示されます。
マネージ関数は、ネイティブ関数の実行中に .NET Framework のガベージ コレクションがデリゲートを再配置することがないように、マネージ デリゲートに対するガベージ コレクションを抑制します。
/clr を指定してマネージ モジュールをコンパイルします。/clr:pure を使用してもかまいません。
// 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);
}
// 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 を使ってインポートされた関数についての問題は、コンパイル時には検出されません。