Consuming Unmanaged C++ Class Libraries from .NET Clients

Introduction

Did you ever face the challenge of calling a regular (unmanaged) C++ class library from managed code? Well, I did, and obviously some of the folks on the discussion board did as well. So, why is it such a challenge?

While the .NET Interop Services offer very good support to integrate C-style DLLs and COM objects directly into your C# or VB.NET code, the same is not true for unmanaged C++ class libraries. If you want to make calls into an unmanaged C++ class library, you definitely have to write a wrapper class, and you have to write it in managed C++. No way out.

In this tutorial, I will introduce a set of three sample projects to demonstrate the most basic concepts of writing a managed wrapper around an unmanaged C++ class library. The diagram below depicts the general architecture:

The sample projects include one method, one property, and one event. If you want to learn the bits and pieces of Managed C++, the section "Managed Extensions for C++ Programming" from the MSDN is highly recommended.

An Unmanaged C++ Library

A simple unmanaged C++ DLL is provided to show how C++ classes can be exported. These are the important points:

To define the symbols that will be exported from a DLL, the __declspec(dllexport) and __declspec(dllimport) statements can be used in the class header file. However, because the header file will be used by the DLL project as well as by prospective client projects, we have to make sure that our symbols are exported from the DLL, but imported by clients. The following code does the trick:

#ifdef UNMANAGED_EXPORTS
   #define UNMANAGED_API __declspec(dllexport)
#else
   #define UNMANAGED_API __declspec(dllimport)
#endif

class UNMANAGED_API CUnmanaged
{
};

If UNMANAGED_EXPORTS is defined as a preprocessor directive in the unmanaged DLL project, this DLL will export all symbols of the class CUnmanaged. A client, however, would not define this prepocessor directive; hence, it would import those symbols.

In C and C++, callback functions are the equivalent of what we call events in other environments. There are different ways of implementing a callback mechanism in an unmanaged C++ class. For this sample, I have chosen to implement a callback class. This means that we define an abstract base class, which has to be overridden by any client that wants to actually provide callback methods. In the sample, this abstract base class happens to be nested:

class UNMANAGED_API CUnmanaged
{
public:
   class UNMANAGED_API CFeedback
   {
   public:
      virtual void OnValueChanged( int nValue ) = 0;
   };

   CUnmanaged( CFeedback* pFeedback );
   void SetValue( int nValue );

private:
   CFeedback* m_pFeedback;
};

The class CUnmanaged maintains a pointer to an instance of a class derived from CUnmanaged::CFeedback. Such a derived class has to be implemented by any client that wants to receive the OnValueChanged callback event.

For our sample, the callback provided by a client will be called from inside SetValue():

void CUnmanaged::SetValue( int nValue )
{
   ...
   m_pFeedback->OnValueChanged( nValue );
}

Once again: The OnValueChanged() handler that is called here must be implemented by a client application. CUnmanaged::CFeedback does not provide an implementation of that handler.

The Managed C++ Wrapper Library

This is the centerpiece of this tutorial: the actual wrapper library. What do we have to do to write a .NET-style wrapper? Regarding the interface to the .NET client applications, we have to provide .NET methods, properties, and delegates/events. Regarding the unmanaged library, we have to provide an implementation of its callback mechanism. So, the main task is the translation between the delegate/event concept on the one hand and the callback concept on the other hand. Compared to this, methods and properties are boilerplate, so I will not focus on them.

The managed wrapper library will contain both managed and unmanaged code. Hence, it needs to be a Mixed Mode DLL. What's that? Mixed Mode is the opposite of Pure Intermediate Language. It allows a DLL to have an explicit entry point as well as static variables, which are required by the C Runtime library. To convert your class library project to Mixed Mode, you have to make some changes in the Linker section of your project's property pages:

  • Add /noentry to the Additional Options field of the Command Line page.
  • Add msvcrt.lib to the Additional Dependencies field of the Input page.
  • Remove nochkclr.obj from the Additional Dependencies field of the Input page.
  • Add __DllMainCRTStartup@12 to the Force Symbol Reference field of the Input page.

Having our wrapper enabled to implement managed as well as unmanaged code, we do exactly this. We will actually provide a managed class to serve as an interface to .NET clients, and an unmanaged class to implement the callback mechanism required by the unmanaged DLL. First, we derive a callback class from the unmanaged DLL's CUnmanaged::CFeedback:

#include "..\Unmanaged\Unmanaged.h"
using namespace ManagedLib;

class Feedback : public CUnmanaged::CFeedback
{
public:
   Feedback( Managed* p );
   void OnValueChanged( int nValue );

private:
   gcroot< Managed* > m_pManaged;
};

Because we are deriving this class from a class defined in our unmanaged DLL, we have to include its header file. Because we are using our managed wrapper class (see definition below), we have to use its namespace, "ManagedLib".

But, the main trick here is using the gcroot template. This actually implements a smart pointer, i.e. a pointer to a variable that frees itself after going out of scope. We need this template here because we want to access a managed object (a pointer to an instance of "Managed") from an unmanaged class, which normally would be impossible. Why would that be impossible? Because managed objects are subject to the garbage collector, their address is not fixed; it will be moved around by the garbage collector. Hence, unmanaged code is unable to maintain a pointer to managed objects. However, .NET provides a handle to managed objects, called GCHandle. To simplify avoiding memory leaks, this is wrapped into a smart pointer, and this smart pointer is called gcroot.

Next comes the definition of our managed class. The following code extract highlights its most important features:

#include "..\Unmanaged\Unmanaged.h"
class Feedback;

namespace ManagedLib
{
   public __delegate void ValueChangedHandler( int i );

   public __gc class Managed
   {
   public:
      Managed();
      ~Managed();
      ...
      __property int get_Value();
      __event void add_ValueChanged
         ( ValueChangedHandler* pValueChanged );
      __event void remove_ValueChanged
         ( ValueChangedHandler* pValueChanged );

   private public:
      __event void raise_ValueChanged( int i );

   private:
      CUnmanaged __nogc* m_pUnmanaged;
      Feedback __nogc* m_pFeedback;
      ValueChangedHandler* m_pValueChanged;
   };
}

A forward declaration introduces the unmanaged class Feedback. Its header file will be included by the .cpp file. Because we implement a managed class, we define a namespace. The delegate as well as the event methods are standard Managed C++ syntax, as is the definition of the property. The method raise_ValueChanged can be made internal by the private public specifier because it will be called only from within the same project. The managed class will create instances of the unmanaged classes CUnmanaged and Feedback, so we define pointers to them. Because these instances are unmanaged, they are not affected by garbage collection, so we use the __nogc specifier for them.

Implementation of the Managed class is straightforward. First, we create instances of both unmanaged classes:

Managed::Managed()
{
   m_pFeedback  = new Feedback( this );
   m_pUnmanaged = new CUnmanaged( m_pFeedback );
}

Note that the instance of Feedback receives a this pointer because it will raise Managed's ValueChanged event. The instance of CUnmanaged, on the other hand, receives a pointer to the Feedback instance because it will call its callback method.

Because both pointers point to unmanaged objects, they have to be deleted manually. The Managed class' destructor takes care of that:

Managed::~Managed()
{
   delete m_pFeedback;
   delete m_pUnmanaged;
}

The mechanism to register delegates as well as the event method have to implemented yet. In terms of design patterns, .NET is using the Observer pattern here, so we implement an add_ as well as a remove_ method.

void Managed::add_ValueChanged( ValueChangedHandler* pValueChanged )
{
   m_pValueChanged = 
      static_cast< ValueChangedHandler* >
      (Delegate::Combine( m_pValueChanged, pValueChanged ));
}

void Managed::remove_ValueChanged( ValueChangedHandler* pValueChanged )
{
   m_pValueChanged = 
      static_cast< ValueChangedHandler* >
      (Delegate::Remove( m_pValueChanged, pValueChanged ));
}

void Managed::raise_ValueChanged( int i )
{
   if( m_pValueChanged != 0 )
      m_pValueChanged->Invoke( i );
}

To complete the managed wrapper, one last piece of code needs to be done: the implementation of the callback method that is responsible for actually raising the .NET event. This happens in the Feedback class that has been derived from the CUnmanaged::CFeedback abstract base class.

void Feedback::OnValueChanged( int nValue )
{
   m_pManaged->raise_ValueChanged( nValue );
}

This callback method will be called by CUnmanaged, and it will raise the event implemented by the Managed class.

A Simple C# Consumer

The simplest part of this tutorial is the consumer. It is that simple because it is completely C#. This is the code:

static void Main(string[] args)
{
   Managed oManaged = new Managed();
   oManaged.ValueChanged
      += new ValueChangedHandler( OnValueChanged );
   oManaged.SetValue( 42 );
}

static void OnValueChanged( int i )
{
   ...
}

An event handler (OnValueChanged) is defined and added to our instance of the Managed class. That's it.

Building and Running the Projects

The source code is provided as a Visual Studio 2003 solution. Load the solution ConsumingUnmanagedCode.sln into VS 2003. Execute "Build Solution," but be aware that you have to execute this twice. This is because after successfully building Unmanaged.dll and Managed.dll during the first run, you may have to add the correct reference to the Managed.dll to the Consumer project. Sorry for this inconvenience...

After having successfully built all three projects, press F5 to run the app and see its output in the debug window.



About the Author

Andreas Wieberneit

I started my IT career during the late 80s, using programming environments such as C, Pascal and dBase. With the advent of the 90s, I discovered C++ and OOP. Since then, I devoted most of my work to build modular systems, composed of DLLs, COM objects, and service applications; to participate in the development of multi-tier business systems; and to communicate with Oracle, SQL-Server and postgreSQL databases, all kinds of hardware devices, as well as smart cards. With the beginning of the new millenium I switched to .NET, where the need to integrate existing components forced me to deal with the secrets of interoperability. During the past years, I shared my working hours between managing software projects and doing hands-on implementation of software systems. Currently, I live and work in Toronto, Canada. You can reach me at awieberneit@yahoo.ca.

Downloads

Comments

  • Return a string to the C# layer

    Posted by Emerson Cardoso on 11/23/2012 11:05am

    How can I convert a string from the unmanaged code, and return it as a .NET String, usign the /oldsyntax for CLR?

    Reply
  • Problems with compilation.. still

    Posted by peng_hc on 04/06/2010 10:39am

    I keep having problems with the compilation...
    that's the error code that Visual Studio Prof 2008 gives me:
    
    1>------ Build started: Project: Managed, Configuration: Debug Win32 ------
    1>Linking...
    1>Feedback.obj : error LNK2028: unresolved token (0A000005) "extern "C" void __clrcall ___CxxCallUnwindDtor(void (__clrcall*)(void *),void *)" (?___CxxCallUnwindDtor@@$$J0YMXP6MXPAX@Z0@Z) referenced in function "public: __clrcall Feedback::Feedback(class ManagedLib::Managed ^)" (??0Feedback@@$$FQAM@P$AAVManaged@ManagedLib@@@Z)
    1>Stdafx.obj : error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ)
    1>Feedback.obj : error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ)
    1>Feedback.obj : error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ)
    1>Managed.obj : error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ)
    1>Managed.obj : error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ)
    1>Feedback.obj : error LNK2019: unresolved external symbol "extern "C" void __clrcall ___CxxCallUnwindDtor(void (__clrcall*)(void *),void *)" (?___CxxCallUnwindDtor@@$$J0YMXP6MXPAX@Z0@Z) referenced in function "public: __clrcall Feedback::Feedback(class ManagedLib::Managed ^)" (??0Feedback@@$$FQAM@P$AAVManaged@ManagedLib@@@Z)
    1>..\bin\Debug\Managed.dll : fatal error LNK1120: 3 unresolved externals
    1>Build log was saved at "file://c:\Documents and Settings\username\Desktop\C# trials\ConsumingUnmanagedCode\Managed\Debug\BuildLog.htm"
    1>Managed - 8 error(s), 0 warning(s)
    2>------ Build started: Project: Consumer, Configuration: Debug Any CPU ------
    2>c:\WINDOWS\Microsoft.NET\Framework\v3.5\Microsoft.Common.targets : warning MSB3245: Could not resolve this reference. Could not locate the assembly "Managed". Check to make sure the assembly exists on disk. If this reference is required by your code, you may get compilation errors.
    2>c:\WINDOWS\Microsoft.NET\Framework\v3.5\Csc.exe /noconfig /unsafe- /checked- /nowarn:1701,1702 /nostdlib- /errorreport:prompt /warn:4 /baseaddress:285212672 /define:DEBUG;TRACE /reference:c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Data.dll /reference:c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.dll /reference:c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\System.Xml.dll /debug+ /debug:full /filealign:4096 /optimize- /out:obj\Debug\Consumer.exe /target:exe /warnaserror- /win32icon:App.ico AssemblyInfo.cs Consumer.cs
    C:\Documents and Settings\pietrobda\Desktop\C# trials\ConsumingUnmanagedCode\Consumer\Consumer.cs(3,7): error CS0246: The type or namespace name 'ManagedLib' could not be found (are you missing a using directive or an assembly reference?)
    
    Compile complete -- 1 errors, 0 warnings
    2>Done building project "Consumer.csproj" -- FAILED.
    ========== Build: 0 succeeded, 2 failed, 1 up-to-date, 0 skipped ==========

    Reply
  • problem with compilation

    Posted by jkpie on 09/20/2009 06:08pm

    Very good and clear article. I was looking for something like this. However, I can not compile the source code. Maybe there was a problem at the time of conversion to VS2008 solution: ///////////////////////////////////////////////// Error 4 error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ) Stdafx.obj Error 5 error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ) Feedback.obj Error 6 error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ) Feedback.obj Error 7 error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ) Managed.obj Error 8 error LNK2001: unresolved external symbol "?.cctor@@$$FYMXXZ" (?.cctor@@$$FYMXXZ) Managed.obj Error 9 error LNK2019: unresolved external symbol "extern "C" void __clrcall ___CxxCallUnwindDtor(void (__clrcall*)(void *),void *)" (?___CxxCallUnwindDtor@@$$J0YMXP6MXPAX@Z0@Z) referenced in function "public: __clrcall Feedback::Feedback(class ManagedLib::Managed ^)" (??0Feedback@@$$FQAM@P$AAVManaged@ManagedLib@@@Z) Feedback.obj Error 3 error LNK2028: unresolved token (0A000005) "extern "C" void __clrcall ___CxxCallUnwindDtor(void (__clrcall*)(void *),void *)" (?___CxxCallUnwindDtor@@$$J0YMXP6MXPAX@Z0@Z) referenced in function "public: __clrcall Feedback::Feedback(class ManagedLib::Managed ^)" (??0Feedback@@$$FQAM@P$AAVManaged@ManagedLib@@@Z) Feedback.obj Error 10 fatal error LNK1120: 3 unresolved externals ..\bin\Debug\Managed.dll

    • problem with compilation

      Posted by KennyDebug on 12/04/2009 04:31pm

      I'm getting the same error - any luck getting it to work?

      Reply
    Reply
  • Regarding the class diagramm

    Posted by smini on 02/26/2008 10:55am

    I think that the black diamond of the composition relationships is positioned at the wrong side. It should be on the side of the class that contains the other class.

    • Re: Regarding the class diagramm

      Posted by Bullock on 04/20/2012 08:06am

      Hmm.. Do you think the black diamond was the cause of the compilation issues.

      Reply
    Reply
  • Debugging problems

    Posted by dafnaz on 09/20/2007 04:06am

    I couldn't debug the Unmanage library, the debugger doesn't recognize the class Each time I set a breakpoint, the break point is turned to a question mark. How can I debug this class, I'm using Visuall 2003.

    Reply
  • Building without /clr:oldSyntax

    Posted by whenrybruce on 02/13/2007 02:55pm

    Thanks to the comments by Sandeep Datta I can now build and run this project under VS2005. I'd like to move away from the old CLR syntax, but get numerous compiler errors if I do. Has anyone updates the project to do this already ? Thanks, Henry

    • Building without /clr:oldSyntax

      Posted by jamait on 04/20/2007 03:09pm

      Hi, Any luck with porting this code to the new syntax? Having serious problems! Any help appricated! :) Martin

      Reply
    Reply
  • How about if the unmanaged C++ DLL has been created before.....

    Posted by sunyajun on 07/21/2006 09:39am

    This article is very good and useful. I downloaded the code and it works very well. But my situation is a little bit different. I have a C++ API DLL compiled with VC6, including some C++ class. I want to use these C++ classes in C#. How to include this DLL in my C#.net project? How to wrap these C++ classes into my managed C++ code (DLL project)?
    
    I noticed that in your sample, the unmanaged C++ classes are written, compiled on .Net platform. And you include the unmanaged.h into the managed.h file.
    
    I already have unmanaged C++ DLL, and I have header file for the unmanaged C++ class. But when I include these unmanaged C++ header file into a managed C++ project (managed.h), it doesn't work. Many compiling errors relating to the unmanaged.h happened. The managed C++ DLL project (wrapper class) can't be compiled.
    
    How to deal with this problem? do I need rewrite the unmanaged C++ class header file? is is because the unmanaged C++ class header are writen in VC6 format? will it work if I rewrite the unmanaged C++ header file? And I don't have the source code for unmanaged C++ class (C++ file).
    
    Thanks.

    • How about if the unmanaged C++ DLL has been created before.....

      Posted by smacinnes on 05/06/2009 03:49pm

      I am in the same situation you are. Did you resolve this?

      Reply
    Reply
  • Doesn't compile under Visual Studio 2005

    Posted by bcs on 04/26/2006 03:28am

    I get some very cryptic linker errors in the Managed project. Has anyone tried this code in VS 2005?

    Reply
  • More info about the mixed DLL problem

    Posted by fares on 01/09/2006 04:23pm

    I found the article very precise and useful (If I only had found it earlier it would have saved me from several nervous breakdowns...) Can you make some comments about the workarounds to the mixed DLL problem proposed by Microsoft? Link: "http://support.microsoft.com/?id=814472" You already mentioned some parts of it: - Add /noentry to the Additional Options field of the Command Line page. - Add msvcrt.lib to the Additional Dependencies field of the Input page. - Remove nochkclr.obj from the Additional Dependencies field of the Input page. - Add __DllMainCRTStartup@12 to the Force Symbol Reference field of the Input page. I find the next part of the solution somewhat confusing. I believe this matter is of great interest for the .NET developing community. It would be great if you can give your expert point of view in this matter. Thank You in advance... Federico (Argentina)

    Reply
  • Nice! What if using MFC?

    Posted by roanmw on 07/27/2005 02:24am

    I think this is a very nice approach. Now, my question is what if you are using a MFC-dll (like I do) for the unmanaged part? I link dynamically to MFC so I have a DllMain implicitly and merging this code into the MFC will yield two of them. Maybe it is possible to rename the DllMain entry in the Unmanaged.cpp and provide the calls to the unmanaged dll with something like "EntryPoint=notDllMain". The only thing is, I don't know where to put that. Would this be a reasonable approach? Thanks.

    Reply
  • Loading, Please Wait ...

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • On-demand Event Event Date: October 23, 2014 Despite the current "virtualize everything" mentality, there are advantages to utilizing physical hardware for certain tasks. This is especially true for backups. In many cases, it is clearly in an organization's best interest to make use of physical, purpose-built backup appliances rather than relying on virtual backup software (VBA - Virtual Backup Appliances). Join us for this webcast to learn why physical appliances are preferable to virtual backup appliances, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds