开发C ++ / CLI描述符类


在C ++ / CLI中,通常使用所谓的描述符类-托管类,该类具有指向本机类的指针作为成员。 本文讨论了基于托管模板的使用,用于管理相应本机对象的生存期的便捷且紧凑的方案。 考虑完成的复杂情况。




目录


引言
1. C ++ / CLI中的基本处理模式
1.1。 析构函数和终结器的定义
1.2。 使用堆栈语义
2.托管模板
2.1。 智能指针
2.2。 使用范例
2.3。 更复杂的完成选项
2.3.1。 终结器锁
2.3.2。 使用SafeHandle
参考文献



引言


C ++ / CLI-.NET Framework的语言之一-很少用于开发大型独立项目。 其主要目的是为与本机(非托管)代码进行.NET交互创建程序集。 因此,被称为描述符类的类被广泛使用,托管类具有指向本机类的指针作为成员。 通常,此类描述符类拥有相应的本机对象,也就是说,它必须在适当的时候将其删除。 使此类免税是很自然的,也就是说,实现System::IDisposable 。 在.NET中,此接口的实现必须遵循一种称为Basic Dispose [Cwalina]的特殊模式。 C ++ / CLI的一个显着特征是,编译器承担了实现此模板的几乎所有例行工作,而在C#中,几乎所有事情都必须由手工完成。



1. C ++ / CLI中的基本处理模式


有两种主要方法可以实现此模板。



1.1。 析构函数和终结器的定义


在这种情况下,必须在托管类中定义析构函数和终结器,其余的由编译器完成。


 public ref class X {    ~X() {/* ... */} //     !X() {/* ... */} //  // ... }; 

特别地,编译器执行以下操作:


  1. 对于类X实现System::IDisposable
  2. X::Dispose()提供了对析构函数的调用,对基类的析构函数的调用(如果有)以及对GC::SupressFinalize()的调用。
  3. 覆盖System::Object::Finalize() ,在此提供对基类的终结器和终结器的调用。

您可以从System::IDisposable显式指定继承,但System::IDisposable定义X::Dispose()



1.2。 使用堆栈语义


如果该类具有freed类型的成员,并且使用堆栈语义声明了该基本Dispose模式,则编译器也可以实现该模式。 这意味着没有大写字母(' ^ ')的类型名称用于声明,并且初始化在构造函数初始化列表中进行,而不是在gcnew 。 堆栈的语义在[Hogenson]中进行了描述。


这是一个例子:


 public ref class R : System::IDisposable { public:    R(/*  */); //  // ... }; public ref class X {    R m_R; //   R^ m_R public:    X(/*  */) //         : m_R(/*  */) //   m_R = gcnew R(/*  */)    {/* ... */} // ... }; 

在这种情况下,编译器将执行以下操作:


  1. 对于类X实现System::IDisposable
  2. X::Dispose()m_R提供对R::Dispose()m_R

最终确定由相应的R类功能确定。 与前面的情况一样,可以显式指定从System::IDisposable继承,但是您System::IDisposable定义X::Dispose() 。 自然地,该类可以具有使用栈的语义声明的其他成员,并且还为其提供了Dispose()调用。



2.托管模板


最后,C ++ / CLI的另一个重要功能使尽可能简化描述符类的创建成为可能。 我们正在谈论托管模板。 它们不是泛型,而是真正的模板(如经典C ++中的模板),但是模板不是本机的,而是托管类。 此类模式的实例化导致创建托管类,该托管类可用作基类或程序集中其他类的成员。 托管模板在[Hogenson]中进行了描述。



2.1。 智能指针


托管模板允许您创建诸如智能指针之类的类,这些类包含指向本机对象的指针作为成员,并在析构函数和终结器中提供对其的删除。 在开发自动释放的描述符类时,此类智能指针可用作基类或成员(自然,使用堆栈语义)。


这是此类模式的示例。 第一个模板是基本模板,第二个模板旨在用作基本类,第三个模板用作该类的成员。 这些模板具有旨在删除对象的模板参数(本机)。 默认情况下,删除类使用delete运算符删除对象。


 //  , -  , T —   template <typename T> struct DefDeleter {    void operator()(T* p) const { delete p; } }; //  , //      //  , T —  , D — - template <typename T, typename D> public ref class ImplPtrBase : System::IDisposable {    T* m_Ptr;    void Delete()    {        if (m_Ptr != nullptr)        {            D del;            del(m_Ptr);            m_Ptr = nullptr;        }    }    ~ImplPtrBase() { Delete(); }    !ImplPtrBase() { Delete(); } protected:    ImplPtrBase(T* p) : m_Ptr(p) {}    T* Ptr() { return m_Ptr; } }; //        template <typename T, typename D = DefDeleter<T>> public ref class ImplPtr : ImplPtrBase<T, D> { protected:    ImplPtr(T* p) : ImplPtrBase(p) {} public:    property bool IsValid    {        bool get() { return (ImplPtrBase::Ptr() != nullptr); }    } }; //        template <typename T, typename D = DefDeleter<T>> public ref class ImplPtrM sealed : ImplPtrBase<T, D> { public:    ImplPtrM(T* p) : ImplPtrBase(p) {}    operator bool() { return ( ImplPtrBase::Ptr() != nullptr); }    T* operator->() { return ImplPtrBase::Ptr(); }    T* Get() { return ImplPtrBase::Ptr(); } }; 


2.2。 使用范例


 class N //   { public:    N();    ~N();    void DoSomething(); // ... }; using NPtr = ImplPtr<N>; //   public ref class U : NPtr //  - { public:    U() : NPtr(new N()) {}    void DoSomething() { if (IsValid) Ptr()->DoSomething(); } // ... }; public ref class V //  -,   {    ImplPtrM<N> m_NPtr; //   public:    V() : m_NPtr(new N()) {}    void DoSomething() { if (m_NPtr) m_NPtr->DoSomething(); } // ... }; 

在这些示例中,类UV无需任何额外的努力就可以释放;它们的Dispose()提供了对delete运算符的调用,以指向N的指针N 第二个选项使用ImplPtrM<> ,允许您在单个描述符类中管理多个本机类。



2.3。 更复杂的完成选项


最终确定是.NET的一个颇有问题的方面。 在正常的应用场景中,不应调用终结器;应该在Dispose()释放资源。 但是在紧急情况下,这可能会发生,终结器应该正确运行。



2.3.1。 终结器锁


如果本机类位于使用LoadLibrary()/FreeLibrary()动态加载和卸载的DLL中,则在卸载DLL之后,存在引用该类实例的未释放对象时,可能会出现这种情况。 在这种情况下,一段时间后,垃圾收集器将尝试完成它们,并且由于DLL被卸载,程序很可能会崩溃。 (一个特征是在显然关闭了应用程序几秒钟后崩溃。)因此,在卸载DLL之后,必须阻止终结器。 只需对基本ImplPtrBase模板进行少量修改即可实现。


 public ref class DllFlag { protected:    static bool s_Loaded = false; public:    static void SetLoaded(bool loaded) { s_Loaded = loaded; } }; template <typename T, typename D> public ref class ImplPtrBase : DllFlag, System::IDisposable { // ...    !ImplPtrBase() { if (s_Loaded) Delete(); } // ... }; 

加载DLL之后,您需要调用DllFlag::SetLoaded(true) ,然后再卸载DllFlag::SetLoaded(false)



2.3.2。 使用SafeHandle


SafeHandle类实现了一个相当复杂且最可靠的终结算法,请参见[Richter]。 可以将ImplPtrBase<>模板重新设计为使用SafeHandle 。 其余模板无需更改。


 using SH = System::Runtime::InteropServices::SafeHandle; using PtrType = System::IntPtr; template <typename T, typename D> public ref class ImplPtrBase : SH { protected:    ImplPtrBase(T* p) : SH(PtrType::Zero, true)    {        handle = PtrType(p);    }    T* Ptr() { return static_cast<T*>(handle.ToPointer()); }    bool ReleaseHandle() override    {        if (!IsInvalid)        {            D del;            del(Ptr());            handle = PtrType::Zero;        }        return true;    } public:    property bool IsInvalid    {        bool get() override        {            return (handle == PtrType::Zero);        }    } }; 


参考文献


[里希特]
里弗特,杰弗里。 使用C#在Microsoft .NET Framework 4.5平台上进行编程。 第四版:。 来自英语 -圣彼得堡:彼得,2016年。


[Cwalina]
茨瓦利纳(Krzhishtov)。 布拉德·艾布拉姆斯。 软件项目的基础结构:可重用.NET库的约定,惯用语和模板。:Transl。 来自英语 -M .: LLC“ I.D. 威廉姆斯,2011年。


[Hogenson]
戈登Hogenson。 C ++ / CLI:.NET环境的Visual C ++语言。 来自英语 -M .: LLC“ I.D. 威廉姆斯,2007年。



Source: https://habr.com/ru/post/zh-CN426411/


All Articles