如何:使用 P/Invoke 封送函数指针

通过使用 .NET Framework P/Invoke 功能,当与未托管的函数互操作时,可以使用托管委托代替函数指针。 不过,建议尽可能使用 C++ 互操作功能。 P/Invoke 几乎不提供编译时错误报告,不是类型安全的,并且实现起来很繁琐。 如果未托管的 API 打包为 DLL,并且源代码不可用,则 P/Invoke 是唯一选项。 否则,请参阅以下文章:

通过使用托管委托代替本机函数指针,可以从托管代码调用将函数指针作为参数的未托管的 API。 编译器自动将委托作为函数指针封送给未托管的函数。 它插入必要的托管/未托管的转换代码。

示例

以下代码由一个非托管模块和一个托管模块组成。 未托管的模块是 DLL,它定义一个名为 TakesCallback 的函数,该函数接受函数指针。 此地址用于执行函数。

// 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);
}

使用传统 #include 指令不会向托管代码公开 DLL 的任何部分。 事实上,DLL 仅在运行时被访问,因此使用 DllImportAttribute 导入的函数问题无法在编译时检测到。

另请参阅

在 C++ 中使用显式 P/Invoke(DllImport 特性)