AddThis Social Bookmark Button

Print

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:

  • Create a managed class, and declare a single member that points to the unmanaged class.
  • For each constructor of the unmanaged class, define a corresponding constructor for the managed class. This creates an instance of the unmanaged class via the unmanaged class via the unmanaged new operator, calling the original constructor.
  • If the managed class holds the only reference to the unmanaged class, define a destructor that calls the delete operator on the member pointer to the unmanaged class.
  • Deal with overloaded operators.
  • For each remaining method in the unmanaged class, declare an identical method that simply delegates the call to the unmanaged version, performing any parameter marshalling, if required.

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;

};

Pages: 1, 2

Next Pagearrow