管理托管(C#)和非托管(C ++)对象的析构函数

时间:2009-12-22 02:12:55

标签: c# c++ unmanaged destructor managed

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

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

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

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

由于

安德鲁

4 个答案:

答案 0 :(得分:8)

任何托管对象的终结器几乎总是只能用作故障保护。作为一般规则,如果您有终结器逻辑,那么您的对象可能需要实现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时都需要调用。

答案 1 :(得分:3)

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

注意这只是避免调用释放的库(即避免运行非托管析构函数):如果非托管库持有的资源在进程关闭时不会自动释放,那么该资源可能会泄露。 (大多数操作系统资源在进程关闭时都会被释放,所以这通常不会引起关注。)正如Adam所说,CLR终结器旨在作为故障保护:你真的想要更确定地释放资源。因此,如果在结构上可行,Igor建议在C#类上实现IDisposable并确定性地处理该对象将是更可取的。

答案 2 :(得分:1)

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

因此,如果您对处理掉电话(并使用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#one相同的模式,除非您可以在托管C ++程序集中使用非托管资源。如果你真的必须在非托管DLL(而不是一个静态的非托管库)中,那么你就会陷入困境,你将遇到相同的关机问题。

答案 3 :(得分:1)

执行此操作的常用方法是从IDisposable

派生您的托管对象

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

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