.NET to C++ Bridge

February 16, 2008

tags: ,
21 comments

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.
Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

21 comments

  1. JoelFebruary 18, 2008 ב 4:54 PM

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

    Reply
  2. Random ReaderFebruary 25, 2008 ב 12:28 AM

    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.

    Reply
  3. BenFebruary 25, 2008 ב 10:12 PM

    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):

    http://www.codeproject.com/KB/COM/simpleclrhost.aspx

    Reply
  4. BenFebruary 25, 2008 ב 10:14 PM

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

    http://support.microsoft.com/kb/828736

    Reply
  5. BenFebruary 26, 2008 ב 6:29 PM

    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.

    Reply
  6. Robert KlajborMarch 9, 2008 ב 11:06 PM

    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?

    Reply
  7. SaschaAugust 28, 2008 ב 1:52 PM

    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 and changed the code in the ILBridge class from this line:

    wchar_t* __MarshaledReturnVal = marshal_to(__ReturnVal);

    to this code snippet:

    msclr::interop::marshal_context^ context;
    const wchar_t* __MarshaledReturnVal = context->marshal_as(__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!

    Reply
  8. Sasha GoldshteinAugust 31, 2008 ב 4:09 AM

    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 :-)

    Reply
    1. AndreasMarch 31, 2014 ב 12:56 PM

      Hi,

      your wrapper approach worked very well for me so far, but it seems that some recent Microsoft update broke that functionality.

      Up to about two months ago, I was able to successfully compile and run both 32 as well as 64 bit builds of the wrapper using Visual Studio 2013 (Update 1). However, after several updates have been auto-installed, it now seems that an access violation in ntdll.dll causes the 32-bit build to crash at startup, before the main method is reached. In detail, the crash happens in the kernel routine LdrGetDllHandleEx(), where an attempt to write to protected memory is caught. The topmost stack item is LdrInitializeThunk().

      Interestingly, it still works perfectly when building for the x64 target. Also, the 32-bit build works when using the Visual Studio 2012 (v110) platform toolset! Thus, there must be some change introduced to the VS2013 (v120) platform toolset by an update which causes the compiler to create crashing builds in 32-bit mode. I was able to reproduce the issue with two other machines, all of which had been updated to the most current Windows patchlevel. Some time ago the bridge worked fine when using the v120 platform toolset for 32-bit builds, now it crashes. Thus, whatever patch is reponsible does appearently not affect the v110 toolset.

      Sasha, are you able to reproduce the issue, or do you have a clue which Microsoft update has caused this behavior? I have a feeling that either your approach is some sort of “hack” not supported anymore by MS, or, more likely, there is a regression introduced by a faulty bugfix / feature change by Microsoft.

      Best regards,
      Andreas

      Reply
      1. Sasha Goldshtein
        Sasha GoldshteinApril 6, 2014 ב 3:13 PM

        Hi Andreas,

        Can you share a repro project with me? You could post it here or use the contact form on the blog to contact me directly.

        Thanks.

        Reply
  9. CraigSeptember 12, 2008 ב 11:07 AM

    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?

    Reply
  10. SamNovember 18, 2008 ב 4:32 PM

    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?

    Reply
  11. Normans BedardMarch 16, 2009 ב 9:11 PM

    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
    #include
    #else
    #include
    #endif

    class NativeHello
    {
    private:
    #ifdef _MANAGED
    gcroot 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

    Reply
  12. Rakhitha KarunarathneJune 12, 2009 ב 11:39 AM

    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

    Reply
  13. Pablo GranadosNovember 24, 2009 ב 9:08 PM

    Is there a solution to download somewhere?

    Reply
  14. BojanJune 13, 2011 ב 1:45 PM

    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

    Reply
  15. Sasha GoldshteinJune 20, 2011 ב 3:04 PM

    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.

    Reply
  16. JeffDecember 6, 2011 ב 9:52 PM

    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?

    Reply
  17. KiranJanuary 27, 2012 ב 9:34 PM

    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,

    Reply
  18. ShangMay 2, 2013 ב 7:28 PM

    VC had “error C3699: ‘^’ : cannot use this indirection” on the line

    gcroot __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?

    Reply