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

Managing the Unmanaged Code

4.67/5 (16 votes)
1 Apr 2009CPOL5 min read 55.4K   730  
Managed C++ wrapper to a native C++ library

Introduction  

What is the best way to integrate a legacy system written in native C++ with a .NET application?  The first thing that comes to our mind is the COM. Yeah we can certainly expose methods from native C++ through COM.  But dealing with COM is not an easy task. There is always one problem, Memory leaks. It’s a pain to find and fix these leaks. Through COM you can just expose a set of methods, so where is OOA? Is there a way to group these methods as a class? I was just exploring in this direction.

So, if C++ has to interact with .NET languages using COM, then what is the purpose of Managed C++ or C++ /CLI?  There is a way to build a managed library which internally wraps some native C++ code. So for the outer world, it looks like a managed library, which internally wraps up the unmanaged code.

The Problem Statement

How to create a VC++ library which can be consumed by all .NET languages and also by all legacy languages like Native C++, Visual Basic 6, Visual FoxPro etc.

The Solution  

There are two known solutions to the above problem:

  1. Expose the required methods through COM/COM+. This is the most widely used method for years.
  2. Write a managed C++/CLI wrapper for the native C++ library. Yes, a managed library is easily accessible to all .NET languages, but legacy languages? To expose it to legacy languages, expose the methods through COM. Even though exposing the methods through COM is a roundabout way of doing it, I assume that the library is going to be used more in .NET languages, and it helps in maintainability.

We'll discuss the second method in this article.

The Benefits

  1. Dealing with COM in C++ is really tedious. 
  2. Memory leaks can be minimized, as we take full advantage of the CLR.  
  3. COM is usually slower and hard to maintain. 
  4. Methods can be grouped as a class, so we can even wrap native C code with object oriented concepts.

The First Step  

So where do I start? I have a sample native C++ application. I want to wrap it in a managed class. The major hurdle we have is dealing with parameters. The operation goes as follows.

  1. Get the parameters using Managed Data types  [System::String^, System::UInt32, etc] 
  2. Marshall the parameters to Unmanaged data types. 
  3. Call the unmanaged methods using these unmanaged parameters. 
  4. Marshall the return values to Managed data type.
  5. Return Managed parameters.  

I know it can get tedious, but if you can take this pain you have a lot to gain.

Using the Code

Let us see a simple sample application in C++/CLI, which wraps a few native C++ methods. The class definition looks as below: 

MC++
public ref class ManagedWrapperClass 
{
private:
	void MarshalString ( System::String ^ s, std::string& os );
public:
	int ManagedAdd(int num1, int num2);
			
	System::String^ ManagedConcat(System::String^ string1,System::String^ string2);
	void ManagedCompareConcat(System::String^ string1, System::String^ string2, 
		[System::Runtime::InteropServices::Out]System::String^ %concatString,
		[System::Runtime::InteropServices::Out] System::Int32 %result);	
};

// The Class which is being wrapped
public class unmanagedClass
{
public:
	unmanagedClass();
	int UnManagedMethodAddNum(int num1, int num2);
	
	std::string UnManagedMethodConcatString
			(std::string string1,std::string string2);
	
	void UnManagedMethodCompareConcat(std::string string1, 
		std::string string2, std::string* concatString, int *result);  
};		

Here I have defined a class in C++/CLI, which exposes three methods. The first method ManagedAdd() is relatively simple which just adds two integers and returns the sum. If you look at the second method (ManagedConcat()), the parameters are System::String^ which is a .NET compatible data type. It requires some marshalling. The third method ManagedCompareConcat() wraps a method which has some OUT parameters, which is basically a pointer variable in C++.

The method definition is as follows:

MC++
int  unmanagedClass::UnManagedMethodAddNum(int num1, int num2)
  {
        return num1+ num2;
  }

std::string unmanagedClass::UnManagedMethodConcatString
		(std::string string1,std::string string2)
  {
       return string1.append(string2);
  }
  
 void unmanagedClass::UnManagedMethodCompareConcat(std::string string1,
std::string string2, std::string * concatString, int *result)
    {
        //concatenating the string and storing it in the out parameter
        
        std::string  string3;
        string3.append(string1);
        string3.append(string2);
        concatString->assign(string3);
        int cmp = strcmp(string1.c_str(),string2.c_str());
        *result = cmp;
    }

The wrapper methods are implemented as below:

MC++
int ManagedWrapperClass::ManagedAdd(int num1,int num2)
    {
        unmanagedClass myClass ;
        return myClass.UnManagedMethodAddNum(num1,num2);
    }

System::String^ ManagedWrapperClass::ManagedConcat
		(System::String ^string1, System::String ^string2)
    {
        //marshalling parameters to unmanaged type
        std::string unmanagedString1, unmanagedString2;
        MarshalString(string1,unmanagedString1);
        MarshalString(string2,unmanagedString2);
        unmanagedClass myClass;

        //Invoking the unManagedmethod
        std::string resultString = 
	    myClass.UnManagedMethodConcatString(unmanagedString1,unmanagedString2);

        //Marshalling return parameter back to managed type
        System::String^ returnString  = gcnew System::String(resultString.c_str()); 
        return returnString;
    }

    void ManagedWrapperClass::ManagedCompareConcat(System::String^
string1, System::String^ string2,
[System::Runtime::InteropServices::Out]System::String^ %concatString,
[System::Runtime::InteropServices::Out] System::Int32 %result)

    {
        //marshalling parameters to unmanaged type
        std::string unmanagedString1, unmanagedString2;
        MarshalString(string1,unmanagedString1);
        MarshalString(string2,unmanagedString2);
 
        // invoking the unmanaged method
        unmanagedClass myClass;
        std::string resultString;
        int resultInt;
        myClass.UnManagedMethodCompareConcat
		(unmanagedString1,unmanagedString2,&resultString,&resultInt);

       //marshalling the OUT parameters
         concatString  = gcnew System::String(resultString.c_str());
         result = resultInt;
    }

        //Method to marshal a CLR compatible System::String to a std::string
        void ManagedWrapperClass::MarshalString ( System::String ^ s, std::string& os ) 
            {
               const char* chars = 
		(const char*)(Marshal::StringToHGlobalAnsi(s)).ToPointer();
               os = chars;
               Marshal::FreeHGlobal( System::IntPtr((void*)chars));
            }

The major hurdle when we do a wrapping will be parameter marshalling. But it’s not hard to find these methods to convert from native data types to .NET compatible data types.

The Second Step

With the above implementation, I'm able to generate a DLL which can be consumed across all .NET languages.  How do I use the same library in other legacy languages like native C++, Visual Basic 6 or Visual FoxPro? Now the problem is simple. How to consume a managed library in unmanaged code. For this, the best method is expose the methods through COM. You can ask me, why I want a Managed wrapper, to access native C++ application in native C++ application. Very true, we have easier ways of consuming an unmanaged library in an unmanaged C++ application.  But, that forces us to have two different libraries or DLLs. One is a managed one; the other is an unmanaged one for doing the same task. Is there a way to have a single library which works across? Yeah there is a way. The best and easiest way to call a managed method in unmanaged code is COM. So here we'll see how to add the COM stuff into this library. Exposing the methods to COM is quite simple. Only addition is an interface, which is required for exposing the methods. Now the class definition looks like follows:

MC++
[ComVisible(true)]
[Guid("1DB44778-857F-47d1-9D4A-D90F109EFF35")]
public interface class IManagedWrapperClass
{
    int ManagedAdd(int num1, int num2);
    System::String^ ManagedConcat(System::String^ string1,System::String^ string2);
    void ManagedCompareConcat(System::String^ string1,
    System::String^ string2,
    [System::Runtime::InteropServices::Out]System::String^ %concatString,
    [System::Runtime::InteropServices::Out] System::Int32 %result);
};

[ComVisible(true)]
[ClassInterface(ClassInterfaceType::None)]
public ref class ManagedWrapperClass : public IManagedWrapperClass
{
private:
        void MarshalString ( System::String ^ s, std::string& os );
public:
        virtual int ManagedAdd(int num1, int num2);
        virtual System::String^ ManagedConcat
    (System::String^ string1,System::String^ string2);
        virtual void ManagedCompareConcat(System::String^ string1,
    System::String^ string2,
    [System::Runtime::InteropServices::Out]System::String^ %concatString,
    [System::Runtime::InteropServices::Out] System::Int32 %result);
};

// The Class which is being wrapped
public class unmanagedClass
{
public:
    unmanagedClass();
    int UnManagedMethodAddNum(int num1, int num2);
    std::string UnManagedMethodConcatString(std::string string1,std::string string2);
    void UnManagedMethodCompareConcat(std::string string1,
    std::string string2, std::string* concatString, int *result);
};

Now we have a library, which can be consumed by all .NET languages as well as all legacy languages which support COM.

Consuming this Library

Consuming this library in .NET languages is quite simple. Add this DLL as a reference to the project and you are ready to go. But consuming in legacy application will take a bit of work. To consume it in native C++, we have to create the TLB file using the command:

RegAsm /tlb:<filename.tlb> "filename.dll"

Or using the TlbExp command.

You can use this TLB file in your C++ application using the #import directive.  

Final Word

There are lots of libraries, with complex low level operations like image processing, networking, etc. still in native C++. Hopefully this method helps us to wrap those libraries as .NET data types to get a seamless access.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)