C#/C++ 异步反向 pinvoke?

C# / C++ Asynchronous reverse pinvoke?

我需要从本机 C/C++ .dll 异步调用 C# 代码

在搜索操作方法时,我发现我可以创建一个 C# 委托并从中获取一个函数指针,我将在我的本机代码中使用它。

问题是我的本机代码需要运行 异步,即在从本机代码创建的单独线程中,这意味着本机代码从在调用委托之前,C# 将 return 到 C#。

我在另一个 SO 问题中读到,有人遇到了这个问题,尽管 MSDN 怎么说,因为由于异步性质,他的委托在被调用之前被垃圾收集他的任务。

我的问题是:在本机代码中创建的线程中使用来自本机代码的函数指针 运行ning 调用 C# 委托真的可行吗?谢谢。

不,这是一个普遍的错误,并不特定于异步代码。它只是 更有可能在你的情况下字节,因为你从来没有 [DllImport] 背后的机制来让你远离麻烦。我会解释为什么会出错,也许会有帮助。

始终需要回调方法的委托声明,这就是 CLR 现在知道调用该方法的方式。您通常使用 delegate 关键字显式声明它,如果非托管代码是 32 位的并且假设该函数是用 C 或 C++ 编写的,您可能需要应用 [UnmanagedFunctionPointer] 属性。声明很重要,这就是 CLR 如何知道您从本机代码传递的参数需要如何转换为它们的托管等价物。如果您的本机代码将字符串、数组或结构传递给回调,则该转换可能会很复杂。

场景在 CLR 中高度 优化,这很重要,因为托管代码不可避免地在非托管操作系统上运行。这些转换有 lot,您看不到它们,因为它们中的大多数都发生在 .NET Framework 代码中。此优化涉及 thunk,一小段自动生成的机器代码,负责调用外部方法或函数。每当您进行使用委托的互操作调用时,都会即时创建 Thunk。在您的情况下,当 C# 代码将委托传递给您的 C++ 代码时。你的 C++ 代码得到一个指向 thunk 的指针,一个函数指针,你存储它并稍后进行回调。

"You store it" 是问题开始的地方。 CLR 不知道您存储了指向 thunk 的指针,垃圾收集器看不到它。 Thunks 需要内存,通常只有几个字节用于机器代码。它们不会永远存在,当不再需要 thunk 时,CLR 会自动释放内存。

"Is no longer needed" 是问题所在,您的 C++ 代码无法神奇地告诉 CLR 它不再进行回调。所以它使用的简单明了的规则是当委托对象被垃圾回收时,thunk 被销毁。

程序员永远都会因为这条规则而陷入困境。他们没有意识到委托对象的生命周期很重要。在 C# 中尤其棘手,它有很多语法糖,使得创建委托对象非常容易。您甚至不必使用 new 关键字或命名委托类型,只需使用目标方法名称就足够了。这种委托对象的生命周期是 pinvoke 调用。在调用完成并且您的 C++ 代码存储了指针后,委托对象不再在任何地方被引用,因此符合垃圾回收条件。

确切地什么时候发生,thunk 被销毁,是不可预测的。 GC 仅在需要时运行。在您拨打电话后可能是纳秒,当然这不太可能,可能是几秒钟。最棘手的是,永远不会。发生在典型的单元测试中,该单元测试不会以其他方式显式调用 GC.Collect() 。单元测试很少对 GC 堆施加足够的压力来引发收集。当您从另一个线程进行回调时,它更有可能 ,隐含的是其他代码在其他线程上 运行,这使得触发 GC 的可能性更大。你会更快地发现问题。尽管如此,thunk 迟早会在真实程序中被销毁。之后,当您在 C++ 代码中进行回调时,Kaboom。

因此,严格的规则是,您必须 存储对委托的引用以避免过早收集问题。做起来非常简单,只需将它存储在声明为 static 的 C# 程序中的变量中。通常足够好,当 C# 代码告诉您的 C++ 代码停止进行回调时,您可能希望将其显式设置回 null,这在您的情况下不太可能。偶尔,您会希望使用 GCHandle.Alloc() 而不是静态变量。