方法: C++ Interop を使用してコールバックおよびデリゲートをマーシャリングする

このトピックでは、Visual C++ を使用した、マネージド コードとアンマネージド コードの間でのコールバックとデリゲート (コールバックのマネージド バージョン) のマーシャリングについて説明します。

次のコード例では、managed, unmanaged #pragma ディレクティブを使用して、同じファイルにマネージド関数とアンマネージド関数を実装します。ただし、関数は別々のファイルで定義することもできます。 アンマネージド関数のみを含むファイルを、/clr (共通言語ランタイムのコンパイル) を使用してコンパイルする必要はありません。

例: マネージド デリゲートをトリガーするためのアンマネージド API を構成する

次の例は、マネージド デリゲートをトリガーするためのアンマネージド API を構成する方法を示しています。 マネージド デリゲートが作成され、相互運用メソッドの 1 つ GetFunctionPointerForDelegate を使用して、デリゲートの基になるエントリ ポイントが取得されます。 その後、このアドレスがアンマネージド関数に渡されます。その際、呼び出しは、それがマネージド関数として実装されているという事実を把握することなく行われます。

なお、pin_ptr (C++/CLI) を使用してデリゲートをピン留めして、ガベージ コレクターによって再配置または破棄されるのを防ぐことも可能です (ただし、必須ではありません)。 早過ぎるガベージ コレクションから保護することは必要ですが、ピン留めを行うと、収集だけでなく再配置も防止されるので、必要以上の保護が提供されることになります。

デリゲートがガベージ コレクションによって再配置された場合、基になるマネージド コールバックには影響はありません。そのため、Alloc を使用してデリゲートへの参照を追加する方法が使用されています。これにより、デリゲートの再配置は許可しつつ、破棄を防ぐことができます。 pin_ptr の代わりに GCHandle を使用することで、マネージド ヒープが断片化する可能性を減らすことができます。

// MarshalDelegate1.cpp
// compile with: /clr
#include <iostream>

using namespace System;
using namespace System::Runtime::InteropServices;

#pragma unmanaged

// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);

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

#pragma managed

public delegate int GetTheAnswerDelegate(int, int);

int GetNumber(int n, int m) {
   Console::WriteLine("[managed] callback!");
   return n + m;
}

int main() {
   GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);
   GCHandle gch = GCHandle::Alloc(fp);
   IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
   ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
   Console::WriteLine("[managed] sending delegate as callback...");

// force garbage collection cycle to prove
// that the delegate doesn't get disposed
   GC::Collect();

   int answer = TakesCallback(cb, 243, 257);

// release reference to delegate
   gch.Free();
}

例: アンマネージド API によって格納される関数ポインター

次の例は前の例と似ていますが、この場合は、指定された関数ポインターがアンマネージ API によって格納されます。そのため、いつでも呼び出すことができるので、任意の時間にわたってガベージ コレクションを抑制する必要があります。 結果として、次の例では、GCHandle のグローバル インスタンスを使用して、関数スコープに依存することなく、デリゲートの再割り当てを防いでいます。 最初の例で説明したように、これらの例では pin_ptr を使用する必要はありません。ただし、この場合、pin_ptr のスコープが 1 つの関数に制限されるので、いずれにしてもその方法は機能しません。

// MarshalDelegate2.cpp
// compile with: /clr
#include <iostream>

using namespace System;
using namespace System::Runtime::InteropServices;

#pragma unmanaged

// Declare an unmanaged function type that takes two int arguments
// Note the use of __stdcall for compatibility with managed code
typedef int (__stdcall *ANSWERCB)(int, int);
static ANSWERCB cb;

int TakesCallback(ANSWERCB fp, int n, int m) {
   cb = fp;
   if (cb) {
      printf_s("[unmanaged] got callback address (%d), calling it...\n", cb);
      return cb(n, m);
   }
   printf_s("[unmanaged] unregistering callback");
   return 0;
}

#pragma managed

public delegate int GetTheAnswerDelegate(int, int);

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

   return n + m + x;
}

static GCHandle gch;

int main() {
   GetTheAnswerDelegate^ fp = gcnew GetTheAnswerDelegate(GetNumber);

   gch = GCHandle::Alloc(fp);

   IntPtr ip = Marshal::GetFunctionPointerForDelegate(fp);
   ANSWERCB cb = static_cast<ANSWERCB>(ip.ToPointer());
   Console::WriteLine("[managed] sending delegate as callback...");

   int answer = TakesCallback(cb, 243, 257);

   // possibly much later (in another function)...

   Console::WriteLine("[managed] releasing callback mechanisms...");
   TakesCallback(0, 243, 257);
   gch.Free();
}

関連項目

C++ Interop (暗黙の PInvoke) の使用