4

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

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

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

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

谢谢

安德鲁

4

4 回答 4

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需要调用。

于 2009-12-22T02:22:39.187 回答
3

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

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

于 2009-12-22T02:20:56.867 回答
1

您应该从托管对象的Dipose方法中删除您的非托管对象。如果您的代码在垃圾收集器到达它之前没有调用,您还应该调用Dispose该方法。亚当罗宾逊的回答更好地说明了这一点。FinalizeDispose

因此,如果您对 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(而不是静态非托管库)中拥有这些,那么您将被卡住,您将遇到相同的关闭问题。

于 2009-12-22T02:17:52.270 回答
1

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

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

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

于 2009-12-22T02:26:21.693 回答