Click here to Skip to main content
13,299,298 members (49,903 online)
Click here to Skip to main content
Add your own
alternative version


160 bookmarked
Posted 15 Mar 2007

How to Marshal a C++ Class

, 15 Mar 2007
Rate this:
Please Sign up or sign in to vote.
An article on how to marshal a C++ class


I recently needed to marshal some legacy C++ classes into a C# project on which I was working. Microsoft provides well documented means to marshal C-functions, and to marshal COM components, but they left out a mechanism to marshal C++ classes. This article documents the discoveries I made and the eventual solution I came up with.


This article assumes the reader is knowledgeable in C# and .NET and is already familiar with PInvoke and marshaling.


I had existing (unmanaged) C++ DLLs which needed to be used with a managed C# project I was working on. Although I had access to the source code of the DLLs, one of the requirements was that the C++ source code and DLLs could not be dramatically altered. This was due to many reasons, including backwards compatibility with existing projects, the code having already been QA'ed, and project deadlines, so converting the original DLLs to be managed C++ DLLs, or converting the classes within the original DLLs to be COM components was out.

Upon first investigating this issue, I was hoping to be able to declare a definition for a C# version of the class and marshal the object back and forth between the managed and unmanaged memory spaces, similar to how a structure is marshaled back and forth. Unfortunately, this is not possible; in my research I discovered that unmanaged C++ classes can't be marshaled and that the best approach is to either create bridge/wrapper C-functions for the public methods of the class and marshal the functions, or to create a bridge DLL in managed C++.

Solution A: Create Bridge Functions in C and Use PInvoke

Suppose we have the following unmanaged C++ class:

    virtual ~CUnmanagedTestClass();
    void PassInt(int nValue);
    void PassString(char* pchValue);
    char* ReturnString();

Running dumpbin on the DLL containing the class yields the following results:

Screenshot - dumpbin_example.jpg
(Click for larger view. We'll cover the results from dumpbin in a moment).

Since the instantiation of a C++ class object is just a pointer, we can use C#'s IntPtr data type to pass unmanaged C++ objects back and forth, but C-functions need to be added to the unmanaged DLL in order to create and dispose instantiations of the class:

// C++:
extern "C" EXAMPLEUNMANAGEDDLL_API CUnmanagedTestClass* CreateTestClass()
    return new CUnmanagedTestClass();

extern "C" EXAMPLEUNMANAGEDDLL_API void DisposeTestClass(
    CUnmanagedTestClass* pObject)
    if(pObject != NULL)
        delete pObject;
        pObject = NULL;

// C#:
static public extern IntPtr CreateTestClass();

static public extern void DisposeTestClass(IntPtr pTestClassObject);
IntPtr pTestClass = CreateTestClass();
pTestClass = IntPtr.Zero; 
// Always NULL out deleted objects in order to prevent a dirty pointer

This allows us to pass the object back and forth, but how do we call the methods of our class? There are two approaches to accessing the methods. The first approach is to use PInvoke and to use CallingConvention.ThisCall. If you go back to the output from dumpbin, you will see the mangled name for the PassInt() method is "?PassInt@CUnmanagedTestClass@@QAEXH@Z". Using CallingConvention.ThisCall, the PInvoke definition of PassInt() is:

static public extern void PassInt(IntPtr pClassObject, int nValue);

The second approach is to create C-functions which act as a bridge for each public method within the DLL...

// C++:
extern "C" EXAMPLEUNMANAGEDDLL_API void CallPassInt(
    CUnmanagedTestClass* pObject, int nValue)
    if(pObject != NULL)

...and marshal each of new C-functions in C#...

// C#:
static public extern void CallPassInt(IntPtr pTestClassObject, int nValue);

I chose to go with the second approach; the name mangling the compiler does means that the first approach is susceptible to breaking if a different compiler is used to compile the C++ DLL (newer version, different vendor, etc...), or if additional methods are added to the class. There is a little extra work involved with the second approach, but I feel the extra work is rewarded by having better maintainable code and code which is less likely to break in the future.

At this point I should point out that I added the bridge functions to the original DLL and recompiled the DLL, but what if the DLL in question is a third party DLL and you don't have access to the sources so you can't recompile the DLL (you only have the rights to redistribute it)? In this scenario I suggest either:

  1. Creating a new DLL in unmanaged C and place the bridge functions within the new DLL.
  2. Create a managed C++ DLL and have it act as the bridge between the C# code and the unmanaged C++ classes (see Solution B further on).

At this point, the C# code to call our C++ class looks like:

// C#:
IntPtr pTestClass = CreateTestClass();
CallPassInt(pTestClass, 42);
pTestClass = IntPtr.Zero;

This is fine as it is, but this isn't very Object-Oriented. Suppose you aren't the only one working on the project? Will other clients of your code remember to dispose the C++ class object via DisposeTestClass()? Will they correctly use an IntPtr created from CreatetestClassDLL() and not some other IntPtr? The next step is to wrap our C# code and PInvoke definitions into a class.

During my investigation, I came across the following newsgroup posting... browse_thread/thread/d4022eb907736cdd/0e74fa0d34947251?lnk=gst&q= C%2B%2B+class&rnum=6&hl=en#0e74fa0d34947251

...and I decided to mirror this approach and create a class in C# called CSUnmanagedTestClass:
// C#:
public class CSUnmanagedTestClass : IDisposable
    #region PInvokes
    static private extern IntPtr CreateTestClass();

    static private extern void DisposeTestClass(IntPtr pTestClassObject);

    static private extern void CallPassInt(IntPtr pTestClassObject, int nValue);
    #endregion PInvokes

    #region Members
    private IntPtr m_pNativeObject; 
    // Variable to hold the C++ class's this pointer
    #endregion Members

    public CSUnmanagedTestClass()
        // We have to Create an instance of this class through an exported 
        // function
        this.m_pNativeObject = CreateTestClass();

    public void Dispose()

    protected virtual void Dispose(bool bDisposing)
        if(this.m_pNativeObject != IntPtr.Zero)
            // Call the DLL Export to dispose this class
            this.m_pNativeObject = IntPtr.Zero;

            // No need to call the finalizer since we've now cleaned
            // up the unmanaged memory

    // This finalizer is called when Garbage collection occurs, but only if
    // the IDisposable.Dispose method wasn't already called.

    #region Wrapper methods
    public void PassInt(int nValue)
        CallPassInt(this.m_pNativeObject, nValue);
    #endregion Wrapper methods

Now, the C# client of this code simply does:

// C#:
CSUnmanagedTestClass testClass = new CSUnmanagedTestClass();

Solution B: Create a Bridge DLL in Managed C++

Another option is to leave the original DLL untouched and create a new DLL in managed C++ to act as a bridge between the managed C# code and the unmanaged C++ classes in the unmanaged DLL. Using the CUnmanagedTestClass within the managed DLL wasn't difficult, and PInvoke definitions weren't required, but the managed C++ syntax and classes which needed to be used was a bit vexing:

// MCPP:

// Forward declariation
class CUnmanagedTestClass;

public ref class CExampleMCppBridge
    virtual ~CExampleMCppBridge();
    void PassInt(int nValue);
    void PassString(String^ strValue);
    String^ ReturnString();

    CUnmanagedTestClass* m_pUnmanagedTestClass;

    : m_pUnmanagedTestClass(NULL)
    this->m_pUnmanagedTestClass = new CUnmanagedTestClass();

    delete this->m_pUnmanagedTestClass;
    this->m_pUnmanagedTestClass = NULL;

void CExampleMCppBridge::PassInt(int nValue)

// C#:
CExampleMCppBridge example = new CExampleMCppBridge();

(and I have to admit, I'm not very fluent in MCPP)


Both approach A and approach B have their own pros and cons. Are you unfamiliar with MCPP? Go with approach A and create C-functions to wrap the public methods of the class and use PInvoke. Can't modify the original DLL and don't want to create PInvode definitions? Create bridge classes in a new MCPP DLL as demonstrated in approach B.


In this article I have presented the reader with a number of different approaches and solutions to the problem of marshaling an unmanaged C++ class to C#. For the sake of brevity I have only included the CallPassInt() examples in this article, however the...


...are in the source code accompanying this article.


This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


About the Author

Software Developer
United States United States
In a nutshell, my forte is Windows, Macintosh, and cross-platform development, and my interests are in UI, image processing, and MIDI application development.

You may also be interested in...

Comments and Discussions

QuestionIntPtr indicates an unlogical address Pin
t7reyeslua15-Apr-10 2:09
membert7reyeslua15-Apr-10 2:09 
AnswerRe: IntPtr indicates an unlogical address Pin
jeffb426-May-10 11:09
memberjeffb426-May-10 11:09 
QuestionProblem Returning a C++ Struct Pin
abhishekdey19857-Mar-10 21:16
memberabhishekdey19857-Mar-10 21:16 
AnswerRe: Problem Returning a C++ Struct Pin
KarstenK7-Mar-10 22:26
memberKarstenK7-Mar-10 22:26 
AnswerRe: Problem Returning a C++ Struct Pin
jeffb428-Mar-10 9:00
memberjeffb428-Mar-10 9:00 
GeneralRe: Problem Returning a C++ Struct Pin
GPUToaster10-Mar-10 18:31
memberGPUToaster10-Mar-10 18:31 
GeneralUnable to load DLL Pin
AlonOren20-Jan-10 1:01
memberAlonOren20-Jan-10 1:01 
GeneralVariable compatability Pin
Taher Hassan3-Jan-09 11:13
memberTaher Hassan3-Jan-09 11:13 
First of all thanks for this nice article.
Just one question, what about variables compatibility. In other words, there is not problem when you are dealing with int, float, double, or bool. I am trying to wrap on function that basically prints output to a file with ostream in c++. I tried to use streamwriter in C# but it did not work
In tips

Taher Hassan
University of Calgary

GeneralUnder Vista Pin
John Andzelik20-Aug-08 4:44
memberJohn Andzelik20-Aug-08 4:44 
GeneralRe: Under Vista Pin
John Andzelik20-Aug-08 6:33
memberJohn Andzelik20-Aug-08 6:33 
GeneralRe: Under Vista Pin
jeffb4220-Aug-08 8:53
memberjeffb4220-Aug-08 8:53 
GeneralRe: Under Vista Pin
John Andzelik21-Aug-08 5:15
memberJohn Andzelik21-Aug-08 5:15 
QuestionWhat about Vista? Pin
John Andzelik19-Aug-08 11:33
memberJohn Andzelik19-Aug-08 11:33 
AnswerRe: What about Vista? Pin
jeffb4219-Aug-08 14:35
memberjeffb4219-Aug-08 14:35 
QuestionA third way? Pin
Neal Andrews22-Nov-07 22:38
memberNeal Andrews22-Nov-07 22:38 
AnswerRe: A third way? Pin
jeffb4210-Jan-08 15:57
memberjeffb4210-Jan-08 15:57 
GeneralNice job!! [modified] Pin
alphons18-May-07 0:01
memberalphons18-May-07 0:01 
GeneralI prefere the c++/cli class wrapping Pin
KenGuru16-Mar-07 6:10
memberKenGuru16-Mar-07 6:10 
GeneralRe: I prefere the c++/cli class wrapping Pin
Al_S20-Aug-09 3:35
memberAl_S20-Aug-09 3:35 
GeneralRe: I prefere the c++/cli class wrapping Pin
Rainer Schuster20-Aug-09 3:41
memberRainer Schuster20-Aug-09 3:41 
GeneralRe: I prefere the c++/cli class wrapping Pin
Al_S20-Aug-09 3:46
memberAl_S20-Aug-09 3:46 
GeneralC++/CLI is a better option in my Opinion Pin
AnandChavali15-Mar-07 19:59
memberAnandChavali15-Mar-07 19:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web03 | 2.8.171207.1 | Last Updated 15 Mar 2007
Article Copyright 2007 by jeffb42
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid