oli*_*ver 4 c# pinvoke delegates
令我惊讶的是,我今天发现了一个强大的功能.因为它看起来好得令人难以置信,所以我想确保它不仅仅是因为一些奇怪的巧合而起作用.
我一直认为当我的p/invoke(到c/c ++库)调用需要一个(回调)函数指针时,我必须在静态c#函数上传递一个委托.例如,在下面我将始终将KINSysFn的委托引用到该签名的静态函数.
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate int KINSysFn(IntPtr uu, IntPtr fval, IntPtr user_data );
Run Code Online (Sandbox Code Playgroud)
并使用此委托参数调用我的P/Invoke:
[DllImport("some.dll", EntryPoint = "KINInit", ExactSpelling = true, CallingConvention = CallingConvention.Cdecl)]
public static extern int KINInit(IntPtr kinmem, KINSysFn func, IntPtr tmpl);
Run Code Online (Sandbox Code Playgroud)
但是现在我只是尝试并传递了一个实例方法的委托,它也起作用了!例如:
public class MySystemFunctor
{
double c = 3.0;
public int SystemFunction(IntPtr u, IntPtr v, IntPtr userData) {}
}
// ...
var myFunctor = new MySystemFunctor();
KINInit(kinmem, myFunctor.SystemFunction, IntPtr.Zero);
Run Code Online (Sandbox Code Playgroud)
当然,我理解在托管代码中,将"this"对象与实例方法一起打包以形成相应的委托,根本没有技术问题.
但让我感到惊讶的是,MySystemFunctor.SystemFunction的"this"对象也找到了本地dll的方式,它只接受静态函数,并没有为"this"对象包含任何工具或打包它与功能一起.
这是否意味着任何此类委托被单独转换(编组?)到静态函数,其中对相应"this"对象的引用在某种程度上是在函数定义中硬编码的?如何在不同的委托实例之间区分,例如,如果我有
var myFunctor01 = new MySystemFunctor();
// ...
var myFunctor99 = new MySystemFunctor();
KINInit(kinmem, myFunctor01.SystemFunction, IntPtr.Zero);
// ...
KINInit(kinmem, myFunctor99.SystemFunction, IntPtr.Zero);
Run Code Online (Sandbox Code Playgroud)
这些都不能指向同一个功能.如果我动态创建无限数量的MySystemFunctor对象怎么办?是否每个这样的委托在运行时"展开"/编译为自己的静态函数定义?
这是否意味着任何此类委托被单独翻译(编组?)到静态函数...
是的,你猜错了.不完全是"静态函数",CLR中有一大堆代码执行这种魔法.它为thunk自动生成机器代码,以适应从本机代码到托管代码的调用.本机代码获取指向该thunk的函数指针.可能必须转换参数值,标准的pinvoke marshaller职责.并且总是随意调整以匹配对托管方法的调用.挖掘存储的委托的Target属性是提供this的一部分.它跳过堆栈帧,绑定到前一个托管帧的链接,因此GC可以看到它再次需要查找对象根.
然而,有一个讨厌的小细节,几乎每个人都陷入困境.当不再需要回调时,这些thunk会再次自动清理.CLR没有得到本机代码的帮助来确定这一点,它发生在委托对象被垃圾收集时.也许你闻到了老鼠的味道,当你的程序发生了什么?
var myFunctor = new MySystemFunctor();
Run Code Online (Sandbox Code Playgroud)
这是方法的局部变量.它不会存活很长时间,下一个系列会破坏它.坏消息,如果本机代码不断通过thunk进行回调,它将不再存在,这是一个很难的崩溃.在进行代码试验时不太容易看到,因为它需要一段时间.
你必须确保不会发生这种情况.在您的类中存储委托对象可能有效,但是您必须确保您的类对象能够存活足够长的时间.无论需要什么,都不能从片段中猜出来.当您还确保再次注销这些回调时,它往往会自行解决,因为这需要存储对象引用以供稍后使用.您也可以将它们存储在静态变量中或使用GCHandle.Alloc(),但这当然会失去匆忙进行实例回调的好处.通过测试正确完成此操作感觉很好,在调用者中调用GC.Collect().
值得注意的是,您通过明确地新建委托来做到这一点.C#语法糖不需要这样做,这使得更难做到这一点.如果仅在将pinvoke调用进入本机代码时发生回调,而不是罕见(如EnumWindows),那么您不必担心它,因为pinvoke marshaller确保委托对象保持引用.
| 归档时间: |
|
| 查看次数: |
150 次 |
| 最近记录: |