P/Invoke - 将委托编组为函数指针 + void*
P/Invoke - Marshaling delegate as function pointer + void*
C 中一个相当常见的习语是函数采用多态闭包将 this 表示为两个参数,一个函数指针和一个 void 指针(作为参数之一传递给函数指针。
取自 GPGME 库的示例:
typedef gpgme_error_t (*gpgme_passphrase_cb_t) (void *hook,
const char *uid_hint,
const char *passphrase_info,
int prev_was_bad,
int fd);
void gpgme_set_passphrase_cb (gpgme_ctx_t ctx,
gpgme_passphrase_cb_t cb,
void *hook_value);
从概念上讲,函数指针加上void 指针代表与C# 中的委托(闭包)相同的东西。在进行这种 P/Invoke 调用时,是否有一种很好的、规范的方式来编组代表?
Is there a nice, canonical way to marshal a delegate when making this sort of P/Invoke call?
您不需要使用 void*
参数,因为 C# 委托是一个闭包。传递 IntPtr.Zero
作为挂钩值。您的 C# 委托仍然需要接受 void*
参数,但它可以简单地忽略它,因为它不需要它。
您实际上可以将委托从 C# 传递到 C 函数指针。你应该用 [UnmanagedFunctionPointer]
属性装饰这个委托。这就是我们如何包装一个采用函数指针的 C 方法:
C 方法:
__declspec(dllexport) globle int EnvAddRouterEx(int (*queryFunction)(void*, char*))
P\Invoke方法:
[DllImport(clipsDllLocation, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern int EnvAddRouterEx(ClipsRouterQueryFunctionDelegate queryFunction);
P\Invoke 代表:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int ClipsRouterQueryFunctionDelegate(IntPtr theEnv, string logicalName);
其他答案都是正确的,但是,我只是想附加一些 C#9 信息:
现在可以在 C# 中使用函数指针,如下所示(需要 unsafe
启用):
static int add(int a, int b) => a + b;
delegate*<int, int, int> pointer = &add;
int result = add(40, 2);
上面代码片段中的函数指针在内部被处理为nint
(或IntPtr
)并且可以在P/Invoke声明中使用:
[DllImport("my_library.dll")]
static extern int CallMyCallback(delegate*<int, int, int> callback, int arg1, int arg2);
my_library.dll
的示例 C++ 实现可以是:
extern "C" __declspec(dllexport) int CallMyCallback(
int (*callback)(int, int),
int arg1,
int arg2)
{
return callback(arg1, arg2);
}
CallMyCallback
可以如下使用(来自 C#):
static int multiply(int a, int b) => a * b;
int result = CallMyCallback(&multiply, -12, 7);
注意:此语言功能不能与 lambda 表达式或非静态方法一起使用(实例方法需要传递隐式 this
指针)。
官方文档还没有,不过你可以看看C#9 GitHub issue/tracker: https://github.com/dotnet/csharplang/issues/191
C 中一个相当常见的习语是函数采用多态闭包将 this 表示为两个参数,一个函数指针和一个 void 指针(作为参数之一传递给函数指针。
取自 GPGME 库的示例:
typedef gpgme_error_t (*gpgme_passphrase_cb_t) (void *hook,
const char *uid_hint,
const char *passphrase_info,
int prev_was_bad,
int fd);
void gpgme_set_passphrase_cb (gpgme_ctx_t ctx,
gpgme_passphrase_cb_t cb,
void *hook_value);
从概念上讲,函数指针加上void 指针代表与C# 中的委托(闭包)相同的东西。在进行这种 P/Invoke 调用时,是否有一种很好的、规范的方式来编组代表?
Is there a nice, canonical way to marshal a delegate when making this sort of P/Invoke call?
您不需要使用 void*
参数,因为 C# 委托是一个闭包。传递 IntPtr.Zero
作为挂钩值。您的 C# 委托仍然需要接受 void*
参数,但它可以简单地忽略它,因为它不需要它。
您实际上可以将委托从 C# 传递到 C 函数指针。你应该用 [UnmanagedFunctionPointer]
属性装饰这个委托。这就是我们如何包装一个采用函数指针的 C 方法:
C 方法:
__declspec(dllexport) globle int EnvAddRouterEx(int (*queryFunction)(void*, char*))
P\Invoke方法:
[DllImport(clipsDllLocation, CallingConvention = CallingConvention.Cdecl), SuppressUnmanagedCodeSecurity]
public static extern int EnvAddRouterEx(ClipsRouterQueryFunctionDelegate queryFunction);
P\Invoke 代表:
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int ClipsRouterQueryFunctionDelegate(IntPtr theEnv, string logicalName);
其他答案都是正确的,但是,我只是想附加一些 C#9 信息:
现在可以在 C# 中使用函数指针,如下所示(需要 unsafe
启用):
static int add(int a, int b) => a + b;
delegate*<int, int, int> pointer = &add;
int result = add(40, 2);
上面代码片段中的函数指针在内部被处理为nint
(或IntPtr
)并且可以在P/Invoke声明中使用:
[DllImport("my_library.dll")]
static extern int CallMyCallback(delegate*<int, int, int> callback, int arg1, int arg2);
my_library.dll
的示例 C++ 实现可以是:
extern "C" __declspec(dllexport) int CallMyCallback(
int (*callback)(int, int),
int arg1,
int arg2)
{
return callback(arg1, arg2);
}
CallMyCallback
可以如下使用(来自 C#):
static int multiply(int a, int b) => a * b;
int result = CallMyCallback(&multiply, -12, 7);
注意:此语言功能不能与 lambda 表达式或非静态方法一起使用(实例方法需要传递隐式 this
指针)。
官方文档还没有,不过你可以看看C#9 GitHub issue/tracker: https://github.com/dotnet/csharplang/issues/191