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