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

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (30投票s)

2006 年 6 月 20 日

Ms-PL

5分钟阅读

viewsIcon

122213

本文简要介绍了 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,当持有锁时返回 truerelease 方法,用于释放锁;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 中缺失的语法支持。当库实现简单且同样有效时,为什么要增加编译器开销呢?一如既往,欢迎提供反馈(无论是批评还是其他)。

© . All rights reserved.