65.9K
CodeProject 正在变化。 阅读更多。
Home

C++/CLI 教程:从 C# 可释放类派生

starIconstarIconstarIconstarIconstarIcon

5.00/5 (24投票s)

2008 年 7 月 5 日

CPOL

3分钟阅读

viewsIcon

72826

downloadIcon

433

本文介绍了从可释放的 C# 基类实现 C++/CLI 类。

引言

上周在工作中,我需要处理一个从 C# 编写的类派生的 C++/CLI 类,该类实现了 IDisposable。 一开始我弄错了(是的,就是几年前写过关于这个主题的书的那个人),我的老板和我花了一些时间在 Reflector 中查看生成的代码,然后才对其进行修复。 那天晚上我回到家,很快就组装了一个简单的项目,以便我可以从一个简单的角度看到整个画面。 我认为发布一篇反映这个问题(并非有意双关)的文章会对其他人有所帮助。

C# 基类

这是一个实现了 IDisposable 的简单 C# 类。 假设该类处理托管和本机资源。

namespace DisposeDemo
{
    [SuppressMessage("Microsoft.Naming", 
        "CA1709:IdentifiersShouldBeCasedCorrectly", 
        MessageId = "Cs", 
        Justification = "Personal preference")]
    public class CsDisposableBase : IDisposable
    {
        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        ~CsDisposableBase()
        {
            Dispose(false);
        }

        /// <summary>
        /// Derived classes need to override this appropriately
        /// </summary>
        /// <param name="disposing">Indicates whether this is a dispose call
        /// or a call invoked from the finalzier</param>
        protected virtual void Dispose(bool disposing)
        {
            if (disposing)
            {
                DisposeManagedResources();
            }

            FreeNativeResources();
        }

        #endregion

        private void DisposeManagedResources()
        {
            this.WriteLine("Disposing managed resources in base class");
        }

        private void FreeNativeResources()
        {
            this.WriteLine("Freeing native resources in base class");
        }

        public CsDisposableBase(String name)
        {
            this.Name = name;
        }

        public String Name { get; private set; }

        protected void WriteLine(String text)
        {
            Console.WriteLine("{0} : {1}", this.Name, text);
        }
    }
}

对于任何从该类继承的 C# 开发人员来说,根据类的要求重写和实现 void Dispose(bool) 是轻而易举的。 但是,从 C++/CLI 来看,情况并非如此。 C++/CLI 不允许您的类中有任何名为“Dispose”的方法。 虽然您可能之前已经在 C++/CLI 中实现了 IDisposable 类,但您可能还没有从 C# IDisposable 基类派生过。 无论如何,诀窍是避免过度思考和使问题复杂化 - 您只需要像往常一样处理它即可。 将托管释放放在析构函数中,将本机释放放在终结器中。

C++/CLI 派生类

这是 C++/CLI 中派生类的实现

namespace CppDisposable
{
  public ref class DisposableDerived : CsDisposableBase
  {
  private:
    void DisposeManagedResourcesDerived()
    {
      WriteLine("Disposing managed resources in derived class");
    }

    void FreeNativeResourcesDerived()
    {
      WriteLine("Freeing native resources in derived class");
    }

  public:
    ~DisposableDerived()
    {
      DisposeManagedResourcesDerived();
      this->!DisposableDerived();
    }

    !DisposableDerived()
    {
      FreeNativeResourcesDerived();
    }

    DisposableDerived(String^ name) : CsDisposableBase(name)
    {
    }

  };
}

乍一看,代码似乎是错误的。 我们如何确保在需要时调用基类释放代码(和终结器)? 让我们编写一些测试代码来看看会发生什么。

int main(array<System::String ^> ^args)
{
  CppDisposable::DisposableDerived object1(
    "Object that gets disposed");

  CppDisposable::DisposableDerived^ object2 =
    gcnew CppDisposable::DisposableDerived("Object that gets GCd");
  return 0;
}

运行结果如下:-

哇。 一切正常。 这是怎么发生的? 答案在于 VC++ 编译器魔术。 使用 Reflector 查看生成的代码向我们表明,编译器已经生成了我们可能不得不自己编写的代码。

Reflector 显示的内容

这是生成的代码(在 Reflector 中查看)

protected override void Dispose([MarshalAs(UnmanagedType.U1)] bool flag1)
{
    if (flag1)
    {
        try
        {
            this.~DisposableDerived();
        }
        finally
        {
            base.Dispose(true);
        }
    }
    else
    {
        try
        {
            this.!DisposableDerived();
        }
        finally
        {
            base.Dispose(false);
        }
    }
}

因此,在 Dispose 调用期间,将发生以下情况

  1. C# 基类 Dispose 将调用 Dispose(true) 并禁止 GC。
  2. C++/CLI 实现将调用 ~DisposableDerived(它释放派生类中的托管资源,然后调用 !DisposableDerived,它释放派生类中的本机资源)。
  3. 现在,调用 base.Dispose(true)(在 finally 块中),它释放基类中的托管和本机资源。

这就是终结期间发生的情况

  1. C# 基类终结器调用 Dispose(false)
  2. C++/CLI 实现调用 !DisposableDerived(它释放派生类中的本机资源)。
  3. 并且调用 base.Dispose(false)(在 finally 块中),它释放基类中的本机资源。

本质上,这段代码正是我们需要编写的 - 编译器为我们生成了它。

结论

我知道这是一篇非常简单的文章,但有时我们会被简单的事情绊倒。 我希望我在我的书中涵盖了这个主题(我在书中谈到了确定性销毁)。 与往常一样,请随时提交反馈(批判或其他方式)。

历史

  • 2008 年 7 月 5 日 - 文章发布。
© . All rights reserved.