AddThis Social Bookmark Button

Print

Writing Managed Wrappers with Managed C++
Pages: 1, 2

Wrapper Construction

In this section of the article, I will show how to build the managed class that will wrap our linked list, so that we can call the code from C#. First, create a new Managed C++ class library project in VS.NET 2003 called ManagedList, copy the files Item.h, Item.cpp, UnmanagedLinkedList.h, and UnmanagedLinkedList.cpp into the project folder, and then add them to the project.



Step 1: Declare a Member Pointer to the Unmanaged Class

The next thing to do is define a managed class called ManagedList, and declare a data member whose type is a pointer to the class being wrapped; that is, an UnmanagedLinkedList*. The code, so far, from ManagedList.h looks like this:

#pragma once
#include "UnmanagedLinkedList.h"
#pragma managed
#using <mscorlib.dll>
using namespace System;

namespace ManagedList
{
  public __gc class ManagedList
  {
  public:
  private:
    UnmanagedLinkedList __nogc* m_pUnmanagedList;
  };
}

Step 2: Wrapping Constructors

One piece of advice I would like to give you in regards to constructors is that you don't have to wrap every constructor of your unmanaged class. Again, writing managed wrappers affords you the ability to refactor and simplify your code. In addition, you should consider if the constructor in question will make sense in the managed environment. In other words, if your constructor is initializing types that you will no longer be using in an managed environment, then the constructor is not needed. These kind of decisions must be made on a case-by-case basis.

Default Constructor

The default constructor is trivial and simply instantiates the class being wrapped:

MList::MList()
{
  m_pUnmanagedList = new UnmanagedLinkedList();
}
Copy Constructor

The copy constructor, on the other hand, brings up some interesting issues. In native C++, we often find it necessary to have a user-defined copy constructor and a copy assignment issue, to perform true "deep-copy" semantics and not just member-wise copying. The way to do this in the .NET Framework, and thus in Managed C++, is to implement the IClonable interface in our MList class:

public __gc class MList : public ICloneable
{
  public:
    MList();
    virtual Object* Clone()
    {
      // Create new instance of the managed list
      MList* mList = new MList();
      
      // calls unmanaged copy constructor
      *(mList->m_pUnmanagedList) = *m_pUnmanagedList;   
      
      // deep copy any other members from this-> to mList->
      return mList;
    }
  private:
    UnmanagedLinkedList __nogc* m_pUnmanagedList;
};

Step 3: Wrapping Destructors

Wrapping destructors is trivial: have the destructor in the managed wrapper call the destructor in the unmanaged class:

~MList() { m_pUnmanagedList->~UnmanagedLinkedList(); }

I hope right now you are asking yourself, "But Sam, .NET is all about love. It is supposed to take care of memory for me with that garbage collection thingie." While that is certainly true for managed code, the CLR and the garbage collector have no idea about unmanaged resources. Our class holds such a reference to the unmanaged class, and therefore we must call the destructor in the unmanaged class explicitly, so that the underlying unmanaged object is destroyed.

Step 4: Dealing with Overloaded Operators

Managed C++ does not allow overloaded operator=(), just as it does not allow copy constructors. The workaround is to define a method named Assign() and call it explicitly:

virtual MList* Assign(MList* otherOne)
{
  if (otherOne != this)
  {
    *m_pUnmanagedList = *(otherOne->m_pUnmanagedList);
    // Deep copy other members
  }
  return this;
}

Step 4: Wrap the Rest of the Member Functions

Believe it or not, we're done with the hard part! All that is left is to wrap the rest of the member functions of the class, if it makes sense to wrap them.. The good news is that almost all of the time, member functions may simply be wrapped by delegating the implementation of the function to the unmanaged class (except for Accessor functions, which I will discuss separately). I also like to rename the managed versions to fit with the Camel and Pascal casing standards of .NET. For a refresher, here are the remaining member functions declared in the header file:

// Member insert methods
void insert( Item *ptr, int value );
void insert_all( const UnmanagedLinkedList &rhs );
void insert_end( int value );
void InsertFront(int value);

// Member remove methods
int  remove( int value );
void remove_front();
void remove_all();

int  isEmpty();
void DisplayList();

The completed implementation looks like the following:

void MList::Insert( Item *ptr, int value )
{
  m_pUnmanagedList->insert(ptr, value);
}

void MList::InsertAll(const UnmanagedLinkedList &rhs)
{
  m_pUnmanagedList->insert_all(rhs);
}

void MList::InsertEnd(int value)
{
  m_pUnmanagedList->insert_end(value);
}

void MList::InsertFront(int value)
{
  m_pUnmanagedList->InsertFront(value);
}

int MList::Remove( int value )
{
  return(m_pUnmanagedList->remove(value));
}

void MList::RemoveFront()
{
  m_pUnmanagedList->remove_front();
}

void MList::RemoveAll()
{
  m_pUnmanagedList->remove_all();
}

void MList::DisplayList()
{
  m_pUnmanagedList->DisplayList();
}
Accessor Functions

The only things left are the accessor functions. There's nothing much to discuss here, other than to note that MC++ has full support for properties, which formalizes the notion of C++ member accessor functions through them. We only have two to consider. Recall these from the unmanaged code:

// Accessors
Item* Front() const { return m_first; }
int Size() { return m_size; }

All we have to do with these is make them full properties in the MC++ header file:

// properties
__property Item* get_Front();
__property int get_Size();

A C# Test Client for the Managed Wrapper

All that remains now is to test our completed wrapper! Open up VS.NET 2003 and create a new Visual C# Console Project named TestManagedList. There is no need to change the default name of Class1, but you do need to add a reference to the DLL project we just built. I assume you know how to bring up the Add Reference dialog, so simply choose the Projects tab, browse to the ManagedList\debug directory, and select ManagedList.dll.

Upon adding the reference, we can add the line of syntactical sugar that allows us to reference names in the namespace with "shortcut" syntax:

using ManagedList;

We are now ready to rock and roll, and test our managed wrapper. The first thing to do is "new up" an instance of the wrapper (ManagedList):

MList theList = new MList();

The next thing to do is to call the methods to insert values at the front and back of the list, and then display it:

// Insert at the front and back and display the results
for (int i=0; i < 10; ++i)
{
  theList.InsertFront(i);
  theList.InsertEnd(i);
}
theList.DisplayList();

The next thing to test out is the property:

int listSize = theList.Size;
Console.WriteLine("The size of the managed list is {0} items", listSize.ToString());

Now it is time to test the operation of the Clone() method, to act as C++'s copy constructor:

MList newList = (MList)theList.Clone();
listSize = newList.Size;
Console.WriteLine("Size of the clone is {0} items", 
                  listSize.ToString());
Console.WriteLine("...and the members are...");
newList.DisplayList();

// test assignment operator
newList.InsertFront(42);
newList.InsertFront(420);
listSize = newList.Size;
Console.WriteLine("Size of the clone is now {0} items", 
                  listSize.ToString());
Console.WriteLine("and the members are:");
newList.DisplayList();

// now assign to the original to test the assignment operator
theList.Assign(newList);
listSize = theList.Size;
Console.WriteLine("Size of the original is now {0} items", 
                  listSize.ToString());
Console.WriteLine("and the members are:");
newList.DisplayList();

Where Are We?

We have concluded our whirlwind three-part look at Managed C++ and its place in the canon of CLR languages. In this article, we used an example piece of unmanaged C++ code, and wrote a managed wrapper for it, step by step. As you saw, this process is not rocket science, but requires close attention to some details to get right. Hopefully, with this material, you may get started writing wrappers for your legacy C++ code so you may use it from C# and VB.NET.

Sam Gentile is a well-known .NET consultant and is currently working with a large firm, using Visual C++ .NET 2003 to develop both unmanaged and managed C++ applications.


Return to ONDotnet.com