在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
实现System::IDisposable
。 - 在
X::Dispose()
提供了对析构函数的调用,对基类的析构函数的调用(如果有)以及对GC::SupressFinalize()
的调用。 - 覆盖
System::Object::Finalize()
,在此提供对基类的终结器和终结器的调用。
您可以从System::IDisposable
显式指定继承,但System::IDisposable
定义X::Dispose()
。
1.2。 使用堆栈语义
如果该类具有freed类型的成员,并且使用堆栈语义声明了该基本Dispose模式,则编译器也可以实现该模式。 这意味着没有大写字母(' ^
')的类型名称用于声明,并且初始化在构造函数初始化列表中进行,而不是在gcnew
。 堆栈的语义在[Hogenson]中进行了描述。
这是一个例子:
public ref class R : System::IDisposable { public: R();
在这种情况下,编译器将执行以下操作:
- 对于类
X
实现System::IDisposable
。 - 在
X::Dispose()
为m_R
提供对R::Dispose()
的m_R
。
最终确定由相应的R
类功能确定。 与前面的情况一样,可以显式指定从System::IDisposable
继承,但是您System::IDisposable
定义X::Dispose()
。 自然地,该类可以具有使用栈的语义声明的其他成员,并且还为其提供了Dispose()
调用。
2.托管模板
最后,C ++ / CLI的另一个重要功能使尽可能简化描述符类的创建成为可能。 我们正在谈论托管模板。 它们不是泛型,而是真正的模板(如经典C ++中的模板),但是模板不是本机的,而是托管类。 此类模式的实例化导致创建托管类,该托管类可用作基类或程序集中其他类的成员。 托管模板在[Hogenson]中进行了描述。
2.1。 智能指针
托管模板允许您创建诸如智能指针之类的类,这些类包含指向本机对象的指针作为成员,并在析构函数和终结器中提供对其的删除。 在开发自动释放的描述符类时,此类智能指针可用作基类或成员(自然,使用堆栈语义)。
这是此类模式的示例。 第一个模板是基本模板,第二个模板旨在用作基本类,第三个模板用作该类的成员。 这些模板具有旨在删除对象的模板参数(本机)。 默认情况下,删除类使用delete
运算符删除对象。
2.2。 使用范例
class N // { public: N(); ~N(); void DoSomething();
在这些示例中,类U
和V
无需任何额外的努力就可以释放;它们的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 {
加载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年。