Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++/CLI

C++/CLI Library classes for interop scenarios

4.89/5 (35 votes)
20 Jun 2006Ms-PL5 min read 1  
The article takes a brief look at some not so commonly used classes such as auto_handle, lock, and ptr.

Introduction

Visual C++ 2005 comes with a bunch of helper classes that provide support for smart pointers, synchronization support, and COM wrappers. Most of them are very handy for interop scenarios, and this article will show examples on using these classes.

The auto_handle class

C++/CLI has syntactic support for deterministic disposal - and you can seemingly declare managed objects on the stack. Now, while this is extremely handy for most scenarios, sometimes, you end up with methods (mostly those in the BCL) that return handles to objects. This forces us to use the ^ syntax and to manually remember to call delete once the object is not required any more. The following code shows an example of one such method.

MC++
void Demo1A()
{
    StreamWriter^ sw;
    try
    {
        sw = File::CreateText("d:\\temp.txt");
        sw->WriteLine("This is a line of text");
    }
    finally
    {
        delete sw;
    }
}

This is where the auto_handle class comes in handy. Add the following #include declaration to your code.

MC++
#include <msclr\auto_handle.h>

Now, you can rewrite the previous method as follows.

MC++
void Demo1B()
{
    msclr::auto_handle<StreamWriter> sw = File::CreateText("d:\\temp.txt");
    sw->WriteLine("This is a line of text");        
}

The auto_handle class overloads the -> operator and returns the underlying handle. So we can call StreamWriter methods just as if we were using a StreamWriter object. And when the destructor is called, delete is called on the underlying handle. If you need to persist the StreamWriter beyond the present scope, you can free the handle from the auto_handle object by calling release on it, as shown below.

MC++
sw.release();

Other operator overloads include those on =, !, and bool, so you can treat it like a pointer. And there's also a swap helper function that swaps handles between two auto_handle objects.

The gcroot and auto_gcroot classes

These two are probably quite well known and very commonly used, specially gcroot, since it existed in the old syntax too. But for the sake of completion, I include these here too. The gcroot template class wraps the BCL GCHandle class and allows us to declare and use a managed object as a member of a non-CLI class. The following code snippet shows a demonstration of the use of this class.

MC++
class Demo2A
{
    msclr::gcroot<StreamWriter^> m_sw;
public:
    Demo2A()
    {
        m_sw = File::CreateText("d:\\temp.txt");
    }
    ~Demo2A()
    {
        delete m_sw;
    }
};

Notice how, I had to manually delete the StreamWriter object in the destructor. Using the auto_gcroot class, we can actually avoid having to do that on our own. Here's a modified class that uses auto_gcroot.

MC++
class Demo2B
{
    msclr::auto_gcroot<StreamWriter^> m_sw;
public:
    Demo2B()
    {
        m_sw = File::CreateText("d:\\temp.txt");
    }
};

Well, now the destructor is gone. For most purposes, you should use the auto_gcroot class instead of the gcroot class, for obvious reasons. Remember to #include <msclr\gcroot.h> or <msclr\auto_gcroot.h> appropriately.

The _safe_bool class

_safe_bool is a value class that can be used in place of bool when providing an operator overload for bool. It prevents an implicit conversion to any integral type. Consider the following class.

MC++
ref class Demo3A
{
public:
    operator bool() 
    {
        return true;
    }
};

Now the following code would compile fine, even when it may not have been intended to do so.

MC++
Demo3A a;
int y = a;

Here's how you would rewrite the class using a _safe_bool.

MC++
ref class Demo3B
{
public:
    operator msclr::_detail_class::_safe_bool() 
    {
        return msclr::_detail_class::_safe_true;
    }
};

Now the following code will not compile.

MC++
Demo3B a;
int y = a;

You can also treat it like a bool, for most purposes.

MC++
if(a == msclr::_detail_class::_safe_false)
{
}

Both auto_handle and auto_gcroot use _safe_bool for their bool operator overloads. You need to #include <msclr\safebool.h> to use this class.

The ptr template - a COM wrapper class

This class is quite handy when you need to use a COM object as a member of a CLI class. The following #include is needed for this class.

MC++
#include <msclr\com\ptr.h>

The below code sample shows how to use the class.

MC++
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
    }
};

The destructor will release the COM object (so you don't need to worry about it). There are several methods provided such as Attach which allows you to associate a COM pointer with a com::ptr object, Detach which makes the com::ptr object give up ownership of the COM object, and Release which releases the COM object. Note that Detach, increases the ref count before returning the interface pointer, and you are then responsible for releasing the object, once you are done using it.

The lock class

C# has the lock keyword which is internally implemented using the BCL Monitor class. C++/CLI does not have equivalent syntactic support, but the support library includes the lock class which (like the C# lock keyword) internally uses the Monitor class. You need to #include the following header file.

MC++
#include <msclr\lock.h>

Now, here's a sample showing how the lock class can be used.

MC++
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);
    }
};

The lock class also comes with methods such as is_locked which returns true if a lock is held, a release method that releases the lock, an acquire method that gets a lock (and throws an exception if the lock could not be obtained), and a try_acquire method which is similar except that it does not throw an exception (instead it returns a bool). The following code shows how an instance of the above class is used from multiple threads, and how try_acquire is used to wait until all the threads are done executing.

MC++
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;
}

I think it's fantastic that C++/CLI allows us to smoothly write library implementations where a language feature does not directly exist.

Mapping delegates to non-CLI class methods

There's a delegate_proxy_factory class declared in <msclr\event.h> that allows you to map an event handler to a non-CLI class's member function. If you've done Windows Forms - MFC interop, then you presumably already know how to do this. But it's very useful even outside MFC Forms interop scenarios, and is also trivial to implement. Here's some code that shows how to map the FileSystemWatcher class's Renamed event to a native class method (albeit with managed arguments).

MC++
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);
    }
};

Conclusion

Essentially, these support library classes, make up for missing syntactic support in C++/CLI. Why add compiler overhead, when library implementations are trivial and equally effective? As usual, feedback (both critical and otherwise) is welcome.

License

This article, along with any associated source code and files, is licensed under The Microsoft Public License (Ms-PL)