如何:使用 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 导入的函数问题无法在编译时检测到。