DCSIMG
.NET to C++ Bridge - All Your Base Are Belong To Us

All Your Base Are Belong To Us

Mostly .NET internals and other kinds of gory details

.NET to C++ Bridge

Most people have encountered the need for interoperability between managed and unmanaged code.  There are plenty of patterns and tutorials which explain every detail of writing managed code which can call into unmanaged code.  The techniques we can use, from most common to least explored, are:

  1. Straightforward P/Invoke (static extern, [DllImport], sprinkle a [MarshalAs] or two and we're set - there are even tools to help);
  2. COM interop (import the type library and you're good to go);
  3. C++/CLI wrapper class;
  4. Calling unmanaged function directly (CALLI instruction with Reflection.Emit).

The opposite way around, however, is something many people struggle with because it's not as sexy and common.  How can we call new managed code from our old legacy native code?  Well, there are again several ways to do it:

  1. Reverse P/Invoke (has to start from .NET delegate passed as callback, so this is only good if the "action" begins in your .NET code);
  2. COM interop (every .NET class can also be a COM object, with or without explicit interfaces);
  3. C++/CLI wrapper classes.

What I want to focus on is the third approach - generating C++/CLI wrapper classes to allow pure unmanaged code interaction with our managed code.  This has to be done in a tiered approach, because there's no direct way for native C++ code to call into managed code.  What we need to go through is the following mechanical process:

  1. Open a C++/CLI class library project and change the settings so it generates an import lib (under Linker->Output);
  2. Write a C++ class (not a .NET reference/value type, i.e. not a ref/value class) which wraps the methods of the original .NET class.  This means that this C++ class has to be compiled to IL, contain a reference to the .NET object (using the gcroot<> template) and delegate all calls to the .NET object.
  3. Write a native C++ class (using #pragma unmanaged, so it's not compiled to IL) which wraps the IL bridge written in step 2 and delegates all calls to it.

image

That last C++ class will also be decorated with __declspec(dllexport), so we can use the resulting class library as a normal native DLL.  Note that marshaling decisions (converting unmanaged types to managed types and vice versa) are made at the IL bridge class, which is aware of both unmanaged and managed code.

This flow seems very complicated and might also appear to have a negative effect on performance.  However, realistically, even though we have a complex flow, most of the path is simple delegation and therefore a good candidate for inlining.  For example, if we have a C# class A, IL bridge B, native C++ class C and a native client D, we're likely to have two extra function calls only: D->C (because of DLL boundaries, but if D lives in the same DLL as C, inlining is likely again; or if PGO is employed, inlining is an option), and then C->B performs the unmanaged to managed transition (which is most of the cost anyway).  After that, the code in the IL bridge is likely to be inlined with the original .NET class if the method on that class is small.

These steps are highly mechanical and annoyingly similar across various classes, so I wanted to see if I can devise an automatic tool for generating these wrappers.  It seems fairly simple once you have a good code generation framework in place; without one, I was able to bake some sample-quality code which takes a managed type and wraps it with an IL bridge and a native C++ class.  It lacks in many areas (such as support for recursively converting structures and other non-primitive types), but I still decided to attach it because I am not at all sure if I will have the time to wrap it up.  So if anyone feels like picking it up from here, or contributing parts of the work, it would be great.

Without further ado, here's a piece of sample output from the tool.  With the following class in place:

public class Calculator
{
    public int Add(int first, int second)
    {
        return first + second;
    }
    public string FormatAsString(float i)
    {
        return i.ToString();
    }
}

Here's the IL bridge generated for this class:

#pragma once
#pragma managed

#include <vcclr.h>

class ILBridge_CppCliWrapper_Calculator {
private:
    //Aggregating the managed class
    gcroot<CppCliWrapper::Calculator^> __Impl;
public:
    ILBridge_CppCliWrapper_Calculator() {
        __Impl = gcnew CppCliWrapper::Calculator;
    }
    int Add(int first, int second) {
        System::Int32 __Param_first = first;
        System::Int32 __Param_second = second;
        System::Int32 __ReturnVal = __Impl->Add(__Param_first, __Param_second);
        return __ReturnVal;
    }
    wchar_t* FormatAsString(float i) {
        System::Single __Param_i = i;
        System::String __ReturnVal = __Impl->FormatAsString(__Param_i);
        wchar_t* __MarshaledReturnVal = marshal_to<wchar_t*>(__ReturnVal);
        return __MarshaledReturnVal;
    }
};

And here's the native exported header and source for the class.  Note that the exported header is callable by any C++ client - that C++ client doesn't have to be compiled with /CLR or even know what .NET is.

//This is the .h file
#pragma once
#pragma unmanaged

#ifdef THISDLL_EXPORTS
#define THISDLL_API __declspec(dllexport)
#else
#define THISDLL_API __declspec(dllimport)
#endif

//Forward declaration for the bridge
class ILBridge_CppCliWrapper_Calculator;

class THISDLL_API NativeExport_CppCliWrapper_Calculator {
private:
    //Aggregating the bridge
    ILBridge_CppCliWrapper_Calculator* __Impl;
public:
    NativeExport_CppCliWrapper_Calculator();
    ~NativeExport_CppCliWrapper_Calculator();
    int Add(int first, int second);
    wchar_t* FormatAsString(float i);
};

//This is the .cpp file
#pragma managed
#include "ILBridge_CppCliWrapper_Calculator.h"
#pragma unmanaged
#include "NativeExport_CppCliWrapper_Calculator.h"

NativeExport_CppCliWrapper_Calculator::NativeExport_CppCliWrapper_Calculator() {
    __Impl = new ILBridge_CppCliWrapper_Calculator;
}
NativeExport_CppCliWrapper_Calculator::~NativeExport_CppCliWrapper_Calculator()
{
    delete __Impl;
}
int NativeExport_CppCliWrapper_Calculator::Add(int first, int second) {
    int __ReturnVal = __Impl->Add(first, second);
    return __ReturnVal;
}
wchar_t* NativeExport_CppCliWrapper_Calculator::FormatAsString(float i) {
    wchar_t* __ReturnVal = __Impl->FormatAsString(i);
    return __ReturnVal;
}
 
The very preliminary sample code used to generate these classes can be downloaded from here as a Visual Studio 2005 solution.  If you play with it please let me know.

Comments

Reflective Perspective - Chris Alcock » The Morning Brew #34 said:

Pingback from  Reflective Perspective - Chris Alcock  &raquo; The Morning Brew #34

# February 18, 2008 10:07 AM

Joel said:

You forgot an option for calling into managed code: Hosting the CLR using CorBindToRuntimeEx

# February 18, 2008 4:54 PM

Random Reader said:

Hosting the CLR only gets the it running inside your process, and gives you some options for fine-grained control of how it operates. You still need to use one of the above methods to make calls to managed functions from your unmanaged code, whether you're hosting it or not.

# February 25, 2008 12:28 AM

Ben said:

Here is another way, you host the CLR and use IDispatch to call into something loaded into it (thats what Windows does in any case):

www.codeproject.com/.../simpleclrhost.aspx

# February 25, 2008 10:12 PM

Ben said:

Here is Microsoft's official COM way of doing it:

support.microsoft.com/.../828736

# February 25, 2008 10:14 PM

Ben said:

Can you make the Calculator class above call into a VB.NET assembly? If I try and do that then the JIT compile throws an exception and dies. I guess with this approach you can call managed C++, but not really assemblies? Very odd.

# February 26, 2008 6:29 PM

Robert Klajbor said:

I have a native visual c++ app ( statically linked mfc ) which calls a managed c++ dll using a lib.

Everything runs fine in VS2008 debug, but when compiled for production it blows off when trying to access the dll.

Any ideas?

# March 9, 2008 11:06 PM

Sascha said:

I spent quite some time now doing research on C++ to .NET bridging using C++/CLI and found your approach and code to be the cleanest from all the examples I looked at. However, getting it to work required some changes which made me wonder if you ever tried compiling above code samples. Or maybe the way I did it was different than intended?

For anyone interested in my working solution, here my approach:

1. I used Visual Studio 2008 and created 3 projects:

- A C# class library called ManagedClasses which contains the Calculator class.

- A C++ class library with CLR support (/clr) called ILBridge which contains the ILBridge_CppCliWrapper_Calculator and NativeExport_CppCliWrapper_Calculator classes.

- A C++ console application without CLR support called NativeApp which uses the ILBridge lib.

Due to changes in the way the marshaling between objects is done in Visual Studio 2008 I added an include for <msclr\marshal.h> and changed the code in the ILBridge class from this line:

wchar_t* __MarshaledReturnVal = marshal_to<wchar_t*>(__ReturnVal);

to this code snippet:

msclr::interop::marshal_context^ context;

const wchar_t* __MarshaledReturnVal = context->marshal_as<const wchar_t*>(__ReturnVal);

delete context;

Also, I had to split up the ILBridge class into header and source file, otherwise the NativeExport class would not compile, because the compiler kept complaining that you cannot use marshal_as in a class that is translated into native code.

Then I added

#using "..\ManagedClasses\bin\Debug\ManagedClasses.dll"

to the ILBridge class header so that it finds the C# assembly.

Finally I took out the #ifdef THISDLL_EXPORTS from the NativeExport class header, since unless you define this symbol it won't set the correct dll export.

After all these changes it worked fine and I learned a lot more on bridging with C++/CLI :-)

Thanks!

# August 28, 2008 1:52 PM

Sasha Goldshtein said:

Thanks for your comment.

Like I said, it's a sample and I've never quite got to finishing it.  If you want to pick it from here and make it an full bridge generator, please feel free to do so :-)

# August 31, 2008 4:09 AM

Craig said:

Thanks for the article, it appears to provide a very clean solution to the bridging problem. However, for me a couple of points are not clear. First, after you have created the library how would you best reference it from native unmanaged code and call its functionality, and second does a dll not require a DllMain section also?

# September 12, 2008 11:07 AM

Sam said:

Hi,

I am so happy to have stumbled upon this article. Thank you so much. The approach looks much simpler than all other interoperability examples that I have seen till now.

Any idea how does this approach compare with the COM one, in terms of performance? Any gains there?

# November 18, 2008 4:32 PM

Normans Bedard said:

I tried something similar but I have linker problem.

C# class (dll library ManageClass.dll ):

public class ClassCSharp

{

public ClassCSharp()

{

}

public void Parle()

{

   TextWriter allo = new StreamWriter("C:\\test.txt");

   allo.Write("aaaa");

   allo.Close();

}

}

I also created a c++ dll library used as a bridge in cli/c++. Header Bridge.h:

#ifdef _MANAGED

#using <ManagedClasse.dll>

#include <vcclr.h>

#else

#include <stddef.h>

#endif

class NativeHello

{

private:

#ifdef _MANAGED

gcroot<ClassCSharp^> hw;

#else

intptr_t hw;

#endif

public:

_declspec(dllexport) NativeHello();

_declspec(dllexport) ~NativeHello();

_declspec(dllexport) void Speak();

};

Source:

include "stdafx.h"

#include "Bridge.h"

NativeHello::NativeHello()

{

hw = gcnew ClassCSharp();

}

NativeHello::~NativeHello()

{

}

void NativeHello::Speak()

{

hw->Parle();

}

And finally, I try to use my bridge dll in a native win32 application console (all this using visual studio 2008).

Source of my console app:

#include "Bridge.h"

int main()

{

NativeHello* test = new NativeHello();

test->Speak();

return 0;

}

when I Build, it compiles but I got this linker error:

Fatal error LNK1302: only support linking safe .netmodules; unable to link iwj/native .netmodule

I reallly dont understand what is the problem. Any ideas ?

normand.bedard () gmail.com

# March 16, 2009 9:11 PM

Rakhitha Karunarathne said:

Great Article! Thanks!

I had some trouble when building this in VS 2005.

Generated code did not compile . Problem was with marshaling of strings but even the solution posted earlier about it did not work for me. So I had to write a manual string conversion function for it. It basically convert wchar_t in to a string character by character.

Some more things to think about in generator.

When a generating wrappers for a number of classes in an assembly. you will also have methods that return objects of managed classes and accept parameters of managed types.

Handling inheritance in managed classes.

Handling managed Exceptions.

Defining Attributes which can be used to annotate managed classes so the managed code cab give control instructions to generator.

Adding facility for making managed classes, generator-aware. So that managed class can generate additional custom unmanaged code in to wrapper bridge and export dll.

/Rakhitha

# June 12, 2009 11:39 AM

Pablo Granados said:

Is there a solution to download somewhere?

# November 24, 2009 9:08 PM

Bojan said:

Hi,

Thanks for starting this article. It is exactly what I need... I want to call managed code within native C++ code. I found the suggested option with C++/CLI wrapper classes the best for me... I did not have problems to compile it (I commented the method " wchar_t* FormatAsString(float i)", since anyway I am going to use this wrapper for other methods written in c#), but i experience problems during run time... It simply crashes saying that some exceptions are not handled... I tried everything I could think of... and could not locate the problem until now...

My goal is to be able to import some c sharp DLLs in the bridge class, and later be able to include the native header and use the implemented functionality in the c sharp DLLs...

Thank you in advance,

Bojan

# June 13, 2011 1:45 PM

Sasha Goldshtein said:

Just letting everyone know that @Bojan and I sorted the problem out via email. The problem was that the managed assemblies were not copied to the native application's path, so it could not find them.

# June 20, 2011 3:04 PM

Jeff said:

Can someone who was successful at using this approach please post a full solution via a URL pointing to all project files, or a simple zip?

# December 6, 2011 9:52 PM

John said:

Jeff, I believe you can look here: 1code.codeplex.com/wikipage

# January 10, 2012 9:26 AM

Kiran said:

Hi there!

I tried using this solution, but does not work for my environment. One small difference is that the application that loads my native library fails to load, if there are mixed dll dependencies -- or so, it seems. It loads perfectly, if i comment out all references to the mixed model dll, in my native library.

Has anyone run into this issue? Any luck?

Thanks,

# January 27, 2012 9:34 PM

Shang said:

VC had "error C3699: '^' : cannot use this indirection" on the line

gcroot<CppCliWrapper::Calculator^> __Impl;

in the ILBridge header, when I add

namespace CppCliWrappere {class Calculator;}

If I don't add the decl, VC had " undeclared identifier" error. I am using VS2010.  Any suggestion?

# May 2, 2013 7:28 PM
Leave a Comment

(required) 

(required) 

(optional)

(required) 


Enter the numbers above: