如何:使用 C++ 互操作封送回调和委托

本主题演示使用 Visual C++ 在托管代码和非托管代码之间封送回调和委托(回调的托管版本)。

以下代码示例使用 managed、unmanaged #pragma 指令在同一文件中实现托管和非托管函数,但也可以在单独的文件中定义这些函数。 仅包含非托管函数的文件不需要使用 /clr(公共语言运行时编译)进行编译。

示例:配置非托管 API 以触发托管委托

以下示例演示如何配置非托管 API 以触发托管委托。 创建托管委托,并使用其中一种互操作方法 GetFunctionPointerForDelegate 来检索此委托的基本入口点。 然后将此地址传递给非托管函数,后者调用它时并不知道它是作为托管函数实现的。

请注意,可以(但非必要)使用 pin_ptr (C++/CLI) 固定委托,防止垃圾收集器重新定位或处置委托。 尽管需要防止过早的垃圾回收,但固定可提供比所需保护更进一步的保护,因为它不仅可以阻止回收,还可以阻止重新定位。

如果委托由垃圾回收重新定位,这将不会影响基础托管回调,因此 Alloc 用于添加对委托的引用,从而允许重新定位委托,同时阻止处置。 使用 GCHandle 而不是 pin_ptr 可以减少托管堆出现碎片可能性。

// 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 无论如何都不起作用,因为它的范围仅限于单个函数。

// 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++ 互操作(隐式 PInvoke)