【问题标题】:Managing destructors of managed (C#) and unmanaged (C++) objects管理托管 (C#) 和非托管 (C++) 对象的析构函数
【发布时间】:2010-12-28 23:12:21
【问题描述】:

我在 c# dll 中有一个托管对象,它维护一个匿名整数句柄,该句柄指向 c++ dll 中的非托管对象。在 c++ dll 中,匿名整数在 std::map 中用于检索非托管 c++ 对象。通过这种机制,我可以使用匿名整数句柄在托管对象和非托管对象之间保持松散的关联。

在托管对象的 finalize 方法(析构函数)中,我调用了非托管 dll 来删除非托管对象。

c# 程序运行时一切正常,但程序退出时出现问题。因为我无法控制托管端的删除操作顺序,所以在任何托管对象之前从内存中删除非托管 dll。因此,当托管对象的析构函数被调用(反过来调用非托管析构函数[至少间接地])时,非托管对象已经被删除并且程序崩溃了。

那么,如何安全地删除与 c# 程序中的托管对象相关联的外部 c++ dll 中的非托管对象。

谢谢

安德鲁

【问题讨论】:

    标签: c# c++ unmanaged destructor managed


    【解决方案1】:

    您应该从托管对象的Dipose 方法中删除您的非托管对象。您还应该从Finalize 方法中调用Dispose,以防您的代码在垃圾收集器到达之前没有调用Dispose。 Adam Robinson 的回答更好地说明了这一点。

    因此,如果您勤于处理 Dispose 调用(并使用 using 块),您就不应该出现关机崩溃。

    编辑:我认为问题实际上是在终结器运行之前卸载非托管 DLL。老“一旦应用程序关闭,就无法保证卸载的顺序”。

    也许您可以尝试在托管 C++ 程序集中使用非托管资源?这样,您就知道 DLL 在完成之前不会爆炸,并且您不必执行丑陋的 P/Invoke 操作。

    这是一个来自 MSDN 的示例:

    ref struct A {
       // destructor cleans up all resources
       ~A() {
          // clean up code to release managed resource
          // ...
          // to avoid code duplication 
          // call finalizer to release unmanaged resources
          this->!A();
       }
    
       // finalizer cleans up unmanaged resources
       // destructor or garbage collector will
       // clean up managed resources
       !A() {
          // clean up code to release unmanaged resource
          // ...
       }
    };
    

    更多http://msdn.microsoft.com/en-us/library/ms177197.aspx

    上述模式与 C# 模式相同,但您可能会在托管 C++ 程序集中使用非托管资源。如果您真的必须在非托管 DLL(而不是静态非托管库)中拥有这些,那么您将陷入困境,您将遇到同样的关闭问题。

    【讨论】:

      【解决方案2】:

      您可以通过在 C# 对象的终结器中检查 Environment.HasShutdownStarted 来快速解决此问题(如果 HasShutdownStarted 为 true,则不调用 C++ DLL/删除 C++ 对象)。如果您不在主 AppDomain 中,则可能需要改为检查 AppDomain.Current.IsFinalizingForUnload(实际上这通常更安全)。

      请注意,这只是避免调用已释放的库(即避免运行非托管析构函数):如果非托管库持有的资源在进程关闭时不会自动释放,则该资源可能会泄漏。 (大多数操作系统资源在进程关闭时被释放,因此这通常不会成为问题。)正如 Adam 指出的那样,CLR 终结器旨在作为故障保护:您确实希望更确定地释放资源。因此,如果结构上可行,Igor 建议在 C# 类上实现 IDisposable 并确定性地 Dispose 对象会更可取。

      【讨论】:

        【解决方案3】:

        执行此操作的通常方法是从 IDisposable 派生托管对象

        当我完成对象时,我总是尝试显式调用object.Dispose,但我不确定在你的情况下是否有必要。我读过的文档不清楚它是否保证在你的 dll 卸载之前调用 Dispose()。

        在我自己的代码中,托管代码域在非托管应用退出之前被明确拆除,因此我不必担心这个特定问题。

        【讨论】:

        • 不,有 no 保证Dispose 将被调用(因为 GC 本身不会对实现该接口的对象执行任何特殊操作)。这就是为什么我展示的终结器逻辑是强烈推荐的模式。
        • Dispose 将在您的 DLL 卸载之前不会被调用,除非有人调用它;也就是说,CLR 将不会为您调用 Dispose。 (某些 .NET 组件,例如 Form,会为其子级调用它。)当对象被垃圾回收时,包括在关闭期间,CLR 调用 Finalize()。但是 Dispose() 纯粹是用户代码的事情。
        • +1 to itowlson;确切地说,预期的模式是任何实现IDisposable 的东西都应该在它管理的任何也实现IDisposable 的成员对象上调用Dispose
        • 感谢您的澄清,在这一点上,MS 文档可能更清楚 IMO。
        【解决方案4】:

        any 托管对象的终结器几乎总是应仅用作故障保护。作为一般规则,如果您有终结器逻辑,那么您的对象可能需要实现IDisposable。实现IDisposable的基本模式是(假设类名是MyClass):

        public class MyClass : IDisposable
        {
            private int extHandle;
        
            public MyClass()
            {
                extHandle = // get the handle
            }
        
            public void Dispose()
            {
                Dispose(true);
        
                GC.SuppressFinalize(this);
            }
        
            protected virtual void Dispose(bool disposing)
            {
                if(disposing)
                {
                    // call dispose() on any managed objects you might have
                }
        
                // release the handle
            }
        
            ~MyClass()
            {
                Dispose(false);
            }
        }
        

        这也意味着创建和使用该对象的任何代码都需要能够管理该对象的生命周期。最简单的方法是将实例包含在 using 块中,如下所示:

        using(MyClass c = new MyClass())
        {
            // do things with c
        }
        

        using 块自动调用对象上的Dispose,因为它在块末尾超出范围。当然,当对象需要存在于单个函数之外时,事情会变得更加复杂。无论如何,只要对象以Dispose 结束,就需要调用。

        【讨论】:

        • 真正的问题是非常松散的耦合。只需一个句柄,应用程序就不会认为存在依赖关系,因此它可以按任何顺序卸载。一件事可以帮助它建立至少一个强引用,以确保托管 DLL 在非托管之前被卸载。这不需要是您当前使用的参考,只需一个强参考就可以了。我一直在开发一个系统,其中包含数千个使用托管 C++ 对 unmaged DLL 的引用,并且没有遇到这个问题。托管 C++ 也可能是一种解决方案。
        • @Jim:遵循推荐的IDisposable 模式应该可以缓解这个问题,因为对象将(或应该)在任何卸载开始之前被释放。
        猜你喜欢
        • 2016-02-21
        • 2021-07-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-31
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多