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

更新 : 2007 年 11 月

このトピックでは、.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 を使ってインポートされた関数についての問題は、コンパイル時には検出されません。

参照

その他の技術情報

C++ での明示的な PInvoke (DllImport 属性) の使用方法