将托管回调传递给 DllImport (ed) 函数
我有一段代码抛出异常,涉及非托管函数调用的垃圾收集委托。这是代码:
// Setup Callback functions
errorCode = gsapi_set_stdio(ghostScriptPtr, new StdioMessageEventHandler(RaiseStdInCallbackMessageEvent), new StdioMessageEventHandler(RaiseStdOutCallbackMessageEvent), new StdioMessageEventHandler(RaiseStdErrCallbackMessageEvent));
if (errorCode >= 0)
{
try
{
//GC.SuppressFinalize(this);
// Init the GhostScript interpreter
errorCode = gsapi_init_with_args(ghostScriptPtr, commandParameters.Length, commandParameters);
// Stop the Ghostscript interpreter
gsapi_exit(ghostScriptPtr);
}
finally
{
// Release the Ghostscript instance handle
gsapi_delete_instance(ghostScriptPtr);
}
}
_Raise... 传递给函数的变量在被函数调用之前被释放。
我不知道我发生了什么,但我将回调更改为变量:
var _RaiseStdInCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdInCallbackMessageEvent);
var _RaiseStdOutCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdOutCallbackMessageEvent);
var _RaiseStdErrCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdErrCallbackMessageEvent);
// Setup Callback functions
errorCode = gsapi_set_stdio(ghostScriptPtr, _RaiseStdInCallbackMessageEventHandler, _RaiseStdOutCallbackMessageEventHandler, _RaiseStdErrCallbackMessageEventHandler);
并最终阻止为:
finally
{
// Release the Ghostscript instance handle
gsapi_delete_instance(ghostScriptPtr);
_RaiseStdInCallbackMessageEventHandler = _RaiseStdOutCallbackMessageEventHandler = _RaiseStdErrCallbackMessageEventHandler = null;
}
它解决了问题。为什么?我不知道。也许这只是一个巧合。我有一种直觉,在finally块中使用变量会导致不尽早处理变量的对象(因为它在finally块中使用)。这是真的吗?无论如何,为 dllimported 函数提供托管回调是正确的方法吗?
谢谢,帕维尔
I had a piece of code that throwed exception with respect garbage collected delegate being called by unmanaged function. This is the code:
// Setup Callback functions
errorCode = gsapi_set_stdio(ghostScriptPtr, new StdioMessageEventHandler(RaiseStdInCallbackMessageEvent), new StdioMessageEventHandler(RaiseStdOutCallbackMessageEvent), new StdioMessageEventHandler(RaiseStdErrCallbackMessageEvent));
if (errorCode >= 0)
{
try
{
//GC.SuppressFinalize(this);
// Init the GhostScript interpreter
errorCode = gsapi_init_with_args(ghostScriptPtr, commandParameters.Length, commandParameters);
// Stop the Ghostscript interpreter
gsapi_exit(ghostScriptPtr);
}
finally
{
// Release the Ghostscript instance handle
gsapi_delete_instance(ghostScriptPtr);
}
}
_Raise... variables passed to function are being disposed before being called by the function.
I don't know what occurred to me, but I changed the callbacks into variables:
var _RaiseStdInCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdInCallbackMessageEvent);
var _RaiseStdOutCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdOutCallbackMessageEvent);
var _RaiseStdErrCallbackMessageEventHandler = new StdioMessageEventHandler(RaiseStdErrCallbackMessageEvent);
// Setup Callback functions
errorCode = gsapi_set_stdio(ghostScriptPtr, _RaiseStdInCallbackMessageEventHandler, _RaiseStdOutCallbackMessageEventHandler, _RaiseStdErrCallbackMessageEventHandler);
and finally block to:
finally
{
// Release the Ghostscript instance handle
gsapi_delete_instance(ghostScriptPtr);
_RaiseStdInCallbackMessageEventHandler = _RaiseStdOutCallbackMessageEventHandler = _RaiseStdErrCallbackMessageEventHandler = null;
}
and it fixed the issue. Why? I don't know. Perhaps it's just a coincidence. I have a gut feeling that using variables in finally block resulted in not disposing variable's object to early (because it's used in finally block). Is that true? Anyway, is it a correct approach to provide dllimported function with managed callbacks?
Thanks,Pawel
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
是的,您的做法是正确的。不完全是。垃圾收集器无法知道本机代码具有对委托的引用。它被埋在由 Marshal.GetFunctionPointerForDelegate() 生成的 thunk 中,超出了 GC 的范围。因此,您有必要拥有另一个对委托的引用,一个 GC可以看到的引用。
您部分地通过使用局部变量来完成此操作,GC 还会遍历堆栈和 CPU 寄存器,并且可以查看委托引用。但是,当您在没有调试器的情况下以发布模式运行代码时,就会出现问题。连接调试器后,抖动会报告局部变量的生命周期,直到方法结束。这使得调试变得更加容易。如果没有调试器,它就不再这样做。即使在finally块中将变量设置为null,抖动优化器也不会删除该分配。优化器在发布版本中启用。
最好的办法是将委托引用存储在类的字段中。并确保您的类对象的生命周期足够长,能够比回调更持久。接下来最好的办法是对局部变量使用 GC.KeepAlive()。
Yes, you're on the right track with this. Not quite. The garbage collector cannot know that the native code has a reference to the delegate. It is buried in a thunk generated by Marshal.GetFunctionPointerForDelegate(), out of reach from the GC. It is therefore necessary that you have another reference to the delegate, one that the GC can see.
You did this partially by using the local variable, the GC also walks the stack and CPU registers and can see the delegate reference. However, this goes wrong when you run your code in Release mode without a debugger. With a debugger attached, the jitter reports the life time of local variables until the end of the method. Which makes debugging a lot easier. It no longer does so without a debugger. Not even when setting the variable to null in the finally block, the jitter optimizer removes that assignment. The optimizer is enabled in the Release build.
Best thing to do is to store the delegate references in a field of your class. And make sure your class object lives long enough to outlast a callback. Next best thing is to use GC.KeepAlive() on the local variables.
您的帖子未包含了解所发生情况所需的所有信息:非托管函数原型、PInvoke 声明。无论如何,看看问题标题,我发现您需要使用 Marshal.GetFunctionPointerForDelegate 方法 - 这是将托管回调传递给非托管代码的方法。
Your post doesn't contain all required information to understand what happens: unmanaged function prototype, PInvoke declarations. Anyway, looking at the question title, I see that you need to use Marshal.GetFunctionPointerForDelegate Method - this is the way to pass managed callback to unmanaged code.