管理托管 (C#) 和非托管 (C++) 对象的析构函数
我在 ac# dll 中有一个托管对象,它维护 c++ dll 中非托管对象的匿名整数句柄。在 C++ DLL 内部,匿名整数在 std::map 中用于检索非托管 C++ 对象。通过这种机制,我可以使用匿名整数句柄维护托管对象和非托管对象之间的松散关联。
在托管对象的 Finalize 方法(析构函数)中,我调用非托管 dll 来删除非托管对象。
C# 程序运行时一切正常,但程序退出时出现问题。因为我无法控制托管端删除操作的顺序,所以非托管 dll 会在任何托管对象之前从内存中删除。因此,当调用托管对象的析构函数(它又[至少间接]调用非托管析构函数)时,非托管对象已被删除,并且程序崩溃。
那么,如何安全地删除与 ac# 程序中的托管对象关联的外部 c++ dll 中的非托管对象。
谢谢
安德鲁
I have a managed object in a c# dll that maintains an anonymous integer handle to an unmanaged object in a c++ dll. Inside the c++ dll, the anonymous integer is used in an std::map to retrieve an unmanaged c++ object. Through this mechanism, I can maintain a loose association between a managed and unmanaged object using an anonymous integer handle.
In the finalize method (destructor) of the managed object I have a call into the unmanaged dll to delete the unmanaged object.
All is well as the c# program runs, but I have a problem when the program exits. Becuase I have no control on the order of delete operations on the managed side, the unmanaged dll is deleted from memory BEFORE any managed object. Thus when the managed object's destructor is called (which in turn calls the unmanaged destructor [at least indirectly]), the unmanaged object has already been deleted and the program crashes.
So how can I safely delete a unmanaged object in an external c++ dll that is associated with a managed object in a c# program.
Thanks
Andrew
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
任何托管对象的终结器几乎应该始终仅用作故障保护。作为一般规则,如果您有终结器逻辑,那么您的对象可能需要实现
IDisposable
。实现 IDisposable 的基本模式是(假设类名为 MyClass):这也意味着创建和使用此对象的任何代码都需要能够管理该对象的生命周期。最简单的方法是将实例包含在
using
块中,如下所示:using
块在对象掉落时自动对对象调用Dispose
块末尾的范围。当然,当对象需要存在于单个函数之外时,事情会变得更加复杂。无论如何,每当对象完成时都需要调用Dispose
。The finalizer of any managed object should almost always be used only as a failsafe. As a general rule, if you have finalizer logic, then your object likely needs to implement
IDisposable
. The basic pattern for implementingIDisposable
is (let's say the class name is MyClass):This also means that whatever code is creating and using this object needs to be able to manage the lifetime of the object. The easiest way is to enclose the instance in a
using
block, like this:The
using
block automatically callsDispose
on the object as it falls out of scope at the end of the block. Things, of course, get more complicated when the object needs to exist outside of a single function. In any case, whenever the object is finished withDispose
needs to be called.您可以通过在 C# 对象的终结器中检查 Environment.HasShutdownStarted 来快速解决此问题(如果 HasShutdownStarted 为 true,则不调用 C++ DLL/删除 C++ 对象)。如果您不在主 AppDomain 中,那么您可能需要检查 AppDomain.Current.IsFinalizingForUnload (事实上,这通常可能更安全)。
请注意,这只是避免调用已释放的库(即避免运行非托管析构函数):如果非托管库持有在进程关闭时不会自动释放的资源,则该资源可能会泄漏。 (大多数操作系统资源都会在进程关闭时释放,因此这通常不会成为问题。)正如 Adam 指出的那样,CLR 终结器旨在作为故障保护:您确实希望更确定地释放资源。因此,如果结构上可行,Igor 建议在 C# 类上实现 IDisposable 并确定性地 Dispose 对象会更可取。
You may be able to solve this quickly by checking Environment.HasShutdownStarted in the finaliser of your C# object (and not calling into the C++ DLL / deleting the C++ object if HasShutdownStarted is true). If you are not in the main AppDomain then you might need to check AppDomain.Current.IsFinalizingForUnload instead (in fact this may be safer in general).
Note this merely avoids calling the freed library (i.e. avoids running the unmanaged destructor): if the unmanaged library was holding a resource that won't automatically be freed on process shutdown, then that resource could be leaked. (Most OS resources are freed on process shutdown, so this will often not be a concern.) And as Adam notes the CLR finaliser is intended as a failsafe: you really want to free resources more deterministically. Therefore, if structurally possible, Igor's suggestion to implement IDisposable on the C# class and deterministically Dispose the object would be preferable.
通常的方法是从 IDisposable
当我完成对象时,我总是尝试显式调用
object.Dispose
,但我不确定这在您的情况下是否必要。我读过的文档并不清楚它是否保证在 dll 卸载之前调用 Dispose() 。在我自己的代码中,托管代码域在非托管应用程序退出之前被显式拆除,因此我不必担心该特定问题。
The usual way to do this is to derive your managed object from IDisposable
I always try to call
object.Dispose
explicitly when I'm done with the object, but I'm not sure that would be necessary in your case. The documentation that I've read is unclear as to whether it gurantees that Dispose() will be called before your dll unloads or not.In my own code, the managed code domain is torn down explicitly before the unmanaged app exits so I don't have to worry about that particular problem.
您应该从托管对象的
Dipose
方法中删除非托管对象。您还应该从Finalize
方法中调用Dispose
,以防您的代码在垃圾收集器到达之前尚未调用Dispose
。亚当·罗宾逊的回答更好地说明了这一点。因此,如果您勤奋地进行 Dispose 调用(并使用
using
块),则不应出现关闭崩溃的情况。编辑:我认为问题实际上是非托管 DLL 在终结器运行之前被卸载。老了“一旦应用程序关闭,就无法保证卸载的顺序”。
也许您可以尝试将非托管资源放在托管 C++ 程序集中?这样你就知道 DLL 在你完成之前不会崩溃,并且你不必做丑陋的 P/Invoke 事情。
以下是来自 MSDN 的示例:
更多信息请参见 http://msdn.microsoft.com /en-us/library/ms177197.aspx
上面的模式与 C# 模式相同,只不过您可能会在托管 C++ 程序集中拥有非托管资源。如果您确实必须将它们放在非托管 DLL(不是静态非托管库)中,那么您就会陷入困境,您将遇到相同的关闭问题。
You should delete your unmanaged object from the
Dipose
method of your managed object. You should also callDispose
out of theFinalize
method in case your code hasn't calledDispose
before the garbage collector got to it. Adam Robinson's answer illustrates that much better.So if you are dilligent with you Dispose calls (and use
using
blocks) you shouldn't have shutdown crashes.Edit: I think the problem is actually the unmanaged DLL getting unloaded before the finalizer runs. Ye old "Once the app is shutting down there are no guarantees as to what order the are unloaded".
Perhaps you can experiment having your unmanaged resources in a managed C++ assembly? That way you know the DLL doesn't go bang before you are finished with it and you don't have to do ugly P/Invoke stuff.
Here is an example from MSDN:
More here http://msdn.microsoft.com/en-us/library/ms177197.aspx
The above is the same pattern as the C# one except you might get away with having the unamanaged resources in the managed C++ assembly. If you really MUST have those in an unmanaged DLL (not a static unmanaged library) then you are stuck, you will have the same shutdown issues.