C++/CLI 库类用于互操作场景






4.89/5 (30投票s)
本文简要介绍了 auto_handle、lock 和 ptr 等不常用类。
引言
Visual C++ 2005 提供了一系列辅助类,用于支持智能指针、同步和 COM 包装器。其中大部分对于互操作场景非常方便,本文将展示使用这些类的示例。
auto_handle
类
C++/CLI 具有确定性销毁的语法支持——并且您似乎可以在栈上声明托管对象。虽然这对大多数场景都非常方便,但有时您会遇到(主要是 BCL 中的)返回对象句柄的方法。这迫使我们使用 ^
语法,并在不再需要对象时手动记住调用 delete
。以下代码展示了其中一个方法的示例。
void Demo1A()
{
StreamWriter^ sw;
try
{
sw = File::CreateText("d:\\temp.txt");
sw->WriteLine("This is a line of text");
}
finally
{
delete sw;
}
}
这时 auto_handle
类就派上用场了。将以下 #include
声明添加到您的代码中。
#include <msclr\auto_handle.h>
现在,您可以将前面的方法重写为如下所示。
void Demo1B()
{
msclr::auto_handle<StreamWriter> sw = File::CreateText("d:\\temp.txt");
sw->WriteLine("This is a line of text");
}
auto_handle
类重载了 ->
运算符,并返回底层句柄。因此,我们可以像使用 StreamWriter
对象一样调用 StreamWriter
方法。当析构函数被调用时,会在底层句柄上调用 delete
。如果您需要在当前作用域之外持久化 StreamWriter
,可以通过调用 release
方法将其句柄从 auto_handle
对象中释放,如下所示。
sw.release();
其他运算符重载包括 =
、!
和 bool
上的重载,因此您可以像指针一样对待它。还有一个 swap
辅助函数,可以在两个 auto_handle
对象之间交换句柄。
gcroot
和 auto_gcroot
类
这两个类可能相当出名且非常常用,尤其是 gcroot
,因为它也存在于旧的语法中。但为了完整起见,我也将它们包含在此处。gcroot
模板类包装了 BCL GCHandle
类,允许我们将托管对象声明为非 CLI 类的成员并使用它。以下代码片段展示了该类的使用示例。
class Demo2A
{
msclr::gcroot<StreamWriter^> m_sw;
public:
Demo2A()
{
m_sw = File::CreateText("d:\\temp.txt");
}
~Demo2A()
{
delete m_sw;
}
};
请注意,我必须在析构函数中手动 delete
StreamWriter
对象。使用 auto_gcroot
类,我们可以避免自己进行此操作。这是一个使用 auto_gcroot
的修改后的类。
class Demo2B
{
msclr::auto_gcroot<StreamWriter^> m_sw;
public:
Demo2B()
{
m_sw = File::CreateText("d:\\temp.txt");
}
};
好吧,现在析构函数消失了。出于大多数目的,您应该使用 auto_gcroot
类而不是 gcroot
类,原因不言而喻。请记得适当地 #include <msclr\gcroot.h>
或 <msclr\auto_gcroot.h>
。
_safe_bool
类
_safe_bool
是一个 value class
,在为 bool
提供运算符重载时,可以使用它代替 bool
。它可以防止隐式转换为任何整型。请考虑以下类。
ref class Demo3A
{
public:
operator bool()
{
return true;
}
};
现在,以下代码将能正常编译,即使可能并非有意为之。
Demo3A a;
int y = a;
这是使用 _safe_bool
重写该类的方法。
ref class Demo3B
{
public:
operator msclr::_detail_class::_safe_bool()
{
return msclr::_detail_class::_safe_true;
}
};
现在,以下代码将无法编译。
Demo3B a;
int y = a;
出于大多数目的,您也可以像对待 bool
一样对待它。
if(a == msclr::_detail_class::_safe_false)
{
}
auto_handle
和 auto_gcroot
都使用 _safe_bool
为其 bool
运算符重载。您需要 #include <msclr\safebool.h>
才能使用此类。
ptr
模板 - COM 包装器类
当您需要将 COM 对象用作 CLI 类的成员时,此类非常方便。以下 #include
是使用此类所必需的。
#include <msclr\com\ptr.h>
下面的代码示例展示了如何使用该类。
ref class ManLink
{
private:
msclr::com::ptr<ILink> lnkptr;
public:
ManLink()
{
lnkptr.CreateInstance(__uuidof(Link)); // Create COM object
}
void Resolve()
{
lnkptr->ResolveLink(. . .); // Invoke COM method
}
};
析构函数将释放 COM 对象(因此您无需担心它)。提供了多个方法,例如 Attach
,它允许您将 COM 指针与 com::ptr
对象关联;Detach
,它使 com::ptr
对象放弃对 COM 对象的拥有权;以及 Release
,它释放 COM 对象。请注意,Detach
在返回接口指针之前会增加引用计数,然后由您负责在使用完对象后释放它。
lock
类
C# 具有 lock
关键字,该关键字内部使用 BCL Monitor
类实现。C++/CLI 没有等效的语法支持,但支持库包含 lock
类,该类(像 C# 的 lock
关键字一样)内部使用 Monitor
类。您需要 #include
以下头文件。
#include <msclr\lock.h>
现在,这里有一个示例,展示了如何使用 lock
类。
ref class Demo4
{
public:
void DoSafeStuff()
{
msclr::lock lk(this);
Console::WriteLine("Starting work on thread {0}",
Thread::CurrentThread->ManagedThreadId);
Thread::Sleep(300); // simulate complex task
Console::WriteLine("Work ended on thread {0}",
Thread::CurrentThread->ManagedThreadId);
}
};
lock
类还附带方法,例如 is_locked
,当持有锁时返回 true
;release
方法,用于释放锁;acquire
方法,用于获取锁(如果无法获取锁则抛出异常);以及 try_acquire
方法,它类似但不会抛出异常(而是返回一个 bool
)。以下代码展示了上述类的实例如何从多个线程中使用,以及 try_acquire
如何用于等待所有线程执行完毕。
int main(array<System::String ^> ^args)
{
Demo4 d;
msclr::lock lk(%d,msclr::lock_later); // defers locking
for(int i=0; i<5; i++,(
gcnew Thread(
gcnew ThreadStart(%d, &Demo4::DoSafeStuff)))->Start());
while(!lk.try_acquire(300));
Console::WriteLine("Exiting main");
return 0;
}
我认为 C++/CLI 能够让我们流畅地编写语言中不存在现有语言特性的库实现,这真是太棒了。
将委托映射到非 CLI 类方法
<msclr\event.h>
中声明了一个 delegate_proxy_factory
类,它允许您将事件处理程序映射到非 CLI 类的成员函数。如果您进行过 Windows Forms - MFC 互操作,那么您可能已经知道如何做到这一点。但它即使在 MFC Forms 互操作场景之外也很有用,并且实现起来也很简单。以下代码展示了如何将 FileSystemWatcher
类的 Renamed
事件映射到原生类方法(尽管带有托管参数)。
class Demo5
{
msclr::auto_gcroot<FileSystemWatcher^> m_fsw;
public:
// Step (1)
// Declare the delegate map where you map
// native method to specific event handlers
BEGIN_DELEGATE_MAP(Demo5)
EVENT_DELEGATE_ENTRY(OnRenamed, Object^, RenamedEventArgs^)
END_DELEGATE_MAP()
Demo5()
{
m_fsw = gcnew FileSystemWatcher("d:\\tmp");
// Step (2)
// Setup event handlers using MAKE_DELEGATE
m_fsw->Renamed += MAKE_DELEGATE(RenamedEventHandler, OnRenamed);
m_fsw->EnableRaisingEvents = true;
}
// Step (3)
// Implement the event handler method
void OnRenamed(Object^, RenamedEventArgs^ e)
{
Console::WriteLine("{0} -> {1}",e->OldName, e->Name);
}
};
结论
本质上,这些支持库类弥补了 C++/CLI 中缺失的语法支持。当库实现简单且同样有效时,为什么要增加编译器开销呢?一如既往,欢迎提供反馈(无论是批评还是其他)。