ONDotNet.com    
 Published on ONDotNet.com (http://www.ondotnet.com/)
 See this if you're having trouble printing code examples


Writing Managed Wrappers with Managed C++

by Sam Gentile
03/29/2004

Welcome back! It has been a very long time since the last article in this three-part series. The second article focused on the ability to mix managed and unmanaged code in the same module, which is an ability that is unique to Managed C++; no other CLR language possesses this capability. In this installment, I will take this one step further. I will show you how to take existing legacy unmanaged C++ code, and make it usable from any CLR language in the managed world. This is accomplished via managed wrappers, which act as a managed proxy for the unmanaged C++, thus allowing that existing code to be used from C#, VB.NET, or any other .NET language. I don't have to tell you how valuable this ability is to businesses that have lots of existing C++ code that they wish to use from C# or VB.NET.

Target Audience

A quick note: this article, and this series, assume that the reader is familiar with the basics of the .NET Framework, including the CLR, and has worked with "managed" languages such as C# and VB.NET. It is also strongly assumed that the reader is also an experienced C++ programmer. It should be noted that this is neither an introductory C++, or .NET article and I cannot provide support for anything beyond this article's contents, including all basic C++ questions.

Related Reading

.NET Framework Essentials
By Thuan L. Thai, Hoang Lam

Let's review something from the second article: unmanaged code, at least on the Microsoft platform, is everything you have been programming for years, before .NET. Unmanaged, or "native" code, includes VB6, COM, Win32, native C++, and so forth. It is code that predated .NET, and therefore has absolutely no knowledge of .NET and cannot directly make use of any managed facilities. Our interest is in taking unmanaged C++ code and making it available for use from any CLR language. The last article showed how to mix unmanaged and managed C++ in the same C++ file, but that was just a step in the ultimate direction I am going in here: making that legacy C++ code accessible directly from C# and VB.NET.

Rationale

Although the benefits of the CLR are absolutely compelling, there are literally billions of lines of native C++ code that work perfectly well and will stay in operation for years to come. Indeed, even the Win32 API itself and the shell functions are all unmanaged code, and will be for years to come (until the release of Longhorn). Much of this code works perfectly well and represents millions of dollars of investment for many companies. As most projects move to .NET, particularly in the areas of web services, ASP.NET web applications, and Windows forms, there is a large desire for businesses to be able to reuse their existing non-UI C++ code from new .NET applications. Typically, in my experience, companies starting with .NET want to utilize the above three technologies to leverage the immense RAD benefits of creating managed user interfaces in a fraction of the time right away, but still "call" or utilize their existing legacy C++ code that may contain significant value. In addition, for many shops facing shrinking IT staffs and budgets, as well as large amounts of native code, the issue of new technology adoption is large, and prohibits whole-scale rewriting.

The Simplest Wrapper

In the last article, I talked, in depth, about something known as It Just Works (IJW)! and noted that through the use of the /clr switch and IJW, that it allows you to take your native C++ code and (mostly) "make it" managed. The output of code compiled with the /clr switch is MSIL.

Using that switch and the IJW technology, we can start to look at the simplest wrapper example.

Suppose we have the following unmanaged C++ class:

class Foo
{
public:
  // constructor
  Foo(void) {}
  // destructor
  ~Foo(void) {}

  // some method
  void DoSomeFoo(){}

};

Knowing what we do about MC++ and IJW from the last two articles, what can we do to this code to make it accessible from managed clients such as C# and VB.NET? Well, we already know that we can mix unmanaged and managed C++ through IJW and the /clr compiler switch. With that knowledge, and the knowledge of design patterns, we can create some sort of proxy object or wrapper that is managed, yet through IJW, communicates with the unmanaged code on our behalf. Our managed wrapper would do this delegation. We would contain a pointer to the unmanaged class, in our case Foo, and wrap it in a ManagedFoo.

Microsoft has outlined a series of steps to produce a managed wrapper in their Migration Guide, which I will distill in this article to the following:

Applying those steps to our simple example produces the following managed wrapper:

__gc class MFoo
{
private:
  Foo * _foo;

public:
  // constructor
  MFoo() { _foo = new Foo();}

  // destructor
  ~MFoo() { delete _foo; }

  // method
  void ManagedDoSomeFoo() { _foo->DoSomeFoo(); }

};

This is all that needs to be done to create a simple managed wrapper, but obviously there are quite a few details that come into the picture for code that is not as trivial as our example. The full details are provided in the Managed Extensions for C++ Migration Guide that Microsoft provides with Visual Studio .NET. As I cannot hope to cover all such cases and details in this short article, I will present a condensed and simplified explanation.

One thing I should note is that you don't have to wrap every single member function of a given unmanaged class. Hopefully, all of my readers are familiar with refactoring techniques, which Martin Fowler describes in his most excellent book and are some of the main tenets of Extreme Programming. Well, creating managed wrappers affords you the opportunity to refactor your code in a way that makes sense to your managed clients. There are many things that one used to have to do with native C++ that no longer need to be done in the world of the CLR. In the process of designing managed wrappers, I encourage you to refactor and only expose methods that you are going to need and use from managed clients. If you don't need something, then don't wrap it. Also, C++ classes typically contain private and helper methods. Since private methods are not accessible to other unmanaged classes, they should not be accessible by managed clients, either. Helper functions fall into a similar category and typically should not be exposed.

The Unmanaged Linked List Example

In order to provide a more substantial example to illustrate the process of writing managed wrappers, I have written a simple C++ class that implements a simple linked list. The list is comprised of items:

class Item
{
public:
  friend class UnmanagedLinkedList;
  // constructors and destructors
  Item(int value, Item *ItemToLinkTo = 0);

  virtual ~Item(void) { delete m_next; }

  int Value() const { return m_iValue; }
  void Next(Item* link) { m_next = link; }
  Item* Next() { return m_next; }

private:
  int m_iValue;
  Item* m_next;
};

inline
Item::Item( int value, Item *item ) : m_iValue( value )
{
  if ( !item )
    m_next = 0;
  else
  {
    m_next = item->m_next;
    item->m_next = this;
  }
}

The class UnmanagedLinkedList looks like the following:

#pragma once
#pragma unmanaged
#include "Item.h"
#define NULL    0

class UnmanagedLinkedList
{
public:
  // Constructors
  UnmanagedLinkedList() 
    : m_first(NULL), m_last(NULL), m_current(0), m_size(0) {}
  // Copy constructor
  UnmanagedLinkedList( const UnmanagedLinkedList &rhs ) :
    m_first( 0 ), m_last( 0 ), 
    m_current( 0 ) 
  { 
    insert_all( rhs ); 
  }
  UnmanagedLinkedList& 
    operator=( const UnmanagedLinkedList &rhs )
  { 
    remove_all(); insert_all( rhs ); 
    return *this; 
  }

  virtual ~UnmanagedLinkedList(void);

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

  // 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();

private:
  Item* m_first;
  Item* m_last;
  Item* m_current;
  Item* GetNewItem(int value);
  int   m_size;

};

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

Copyright © 2009 O'Reilly Media, Inc.