Unmanaged Exports

The Ultimate Guide to Calling C# .NET Methods and Classes from Native C/C++ Using Unmanaged Exports

Introduction

Unmanaged Exports is a technique of exporting C# .NET methods from managed C# code to native C++ applications, which means those managed C# methods will be available to other languages such as C/C++, Delphi, Java, Python, PHP, etc.

The technique of making a C# .NET assembly exporting C# methods was disclosed first time in the book of Expert .NET 2.0 IL Assembler published in 2006 and authored by Serge Lidin.

The book is on also google book and you may be able to read the chapter (Chapter 18: Managed and Unmanaged Code Interoperation) describing the technique of creating such exportable .NET function by writing IL Assembly Language code. I believe the name was mentioned first time in that book.

In another book titled .NET IL Assembler published in 2014, Serge Lidin continued the discussion of this technique (Chapter 18: Managed and Unmanaged Code Interoperation).

Excerpts from the book,

Managed Methods as Unmanaged Exports

Exposing managed methods as unmanaged exports provides a way for unmanaged, non-COM clients to consume managed services. In fact, this technique opens the managed world in all its glory—with its secure and type-safe computing and with all the wealth of its class libraries—to unmanaged clients.

Of course, the managed methods are not exposed as such. Instead, inverse P/Invoke thunks, automatically created by the common language runtime, are exported. These thunks provide the same marshaling functions as “conventional” P/Invoke thunks, but in the opposite direction.

In order to expose managed methods as unmanaged exports, the IL assembler builds a v-table, a v-table fixup (VTableFixup) table, and a group of unmanaged export tables, which include the Export Address table, the Name Pointer table, the Ordinal table, the Export Name table, and the Export Directory table. Chapter 4 discusses all of these tables, their structures, and their positioning within a managed PE file. Now let’s see how it all is done.

Manually writing such unmanaged exports method or changing the existing .NET code using IL Assembly Language code is tedious and time-consuming. Robert Giesecke released a nuget package which provides a set of compile-time libraries (nothing to deploy) and a build task that enable you to export functions from managed code to native applications. Robert Giesecke actually said on stack overflow that he used the book “.Net 2.0 IL Assembler” to make sure that he didn’t end up doing any cargo cult stuff.

The procedure of exporting managed method is shown as below in a few simple steps.

  • Create a new C# class library or proceed with an existing .NET assembly.
  • Add Robert Giesecke‘s UnmanagedExports Nuget package by using Package Manager Console.

This will add the following assembly named RGiesecke.DllExport.Metadata.dll to the reference of the .NET project.

It will also modify the .NET project file by adding the new Import node.

  • Write any kind of static method, decorate it with [DllExport] and use it from native code.

  • During compilation, the Nuget task will modify the IL to add the required exports, the .export directive exactly. The process won’t output anything if everything goes well.

Robert Giesecke‘s UnmanagedExports Nuget package has been open sourced on github by both Robert Giesecke and Denis Kuzmin. It is under the MIT License (MIT).

 

Simple Example of Exporting C# Methods with Primitive Data Types only

First let’s create a new C# .NET assembly project. It will be a simple C# .NET assembly containing only one class named Calculator. Although the example is very simple, it is basically a guide of how to use unmanaged exports.

Creating C# dllexport example application

Once we create the C# assembly project, we must make sure the platform target is either set to x86 or x64, it can not be AnyCPU.

x86 or x64 must be defined to export C# method from .NET to native C++ application

And here is the complete code for this C# library.

There is nothing special about the Calculator class, the only thing you may have already noticed is all the 4 methods of Calculator are static methods. This is because .export directive can only be applied to static methods and only static methods can be exported.

Now, let’s install Robert Giesecke‘s UnmanagedExports nuget package.

Install rgiesecke.dllexport nuget example

Let’s add the namespace of RGiesecke.DllExport and then decorate each of the static methods with DllExport attribute. (DllExportAttribute)

By default, C and C++ uses __cdecl calling convention, so we define the C# method as __cdecl calling convention as well.

Now building this C# library project creates a .NET assembly. When we open the DLL by using CFF Explorer Suite, we can see all the 4 methods exported from the DLL as shown below. The DLL becomes a mixed mode assembly after being processed by MSBuild task.

Exporting C# method by using Unmanaged Exports

I tried to find the .export directive by opening the .NET assembly from ILSpy, but I was not able to find it.

ILSpy does not show .export attribute

ILSpy does not show the .export directive. I then tried ILDASM and I was finally able to find both the .vtentry and .export directive as shown below. The following screen-shots demonstrate we have accomplished Native DLL Exports from .NET Assembly.

ILDASM does show .export directive

Now that we have a mixed mode .NET assembly, let’s try to use it from native C/C++ code by creating a C/C++ console application.

image.png

image.png

image.png

Now you are looking at the initial code created by Visual Studio as shown below.

We will need to add the c function declarations for the 4 static methods we export from the .NET assembly in mixed mode. Since those methods are exported as C-style functions, we will need to use extern “C” statement. For the primitive types like integer, you just map C# int to C++ int, since it is cdecl calling convention and we use exactly method name as the export name, and C/C++ default calling convention is cdecl, we do not need to decorate the function with calling convention such as __cdecl, __stdcall, etc.

Before we can start using these methods, we will must link the lib file by setting up additional library directory and lib file.

image.png

image.png

Let’s add some additional code to use these 4 external methods exported from the C# assembly. The complete C/C++ code is shown below.

Before we start running this console application, we would want to copy the C# .NET assembly to where the console application is located. We do this in the command line of post build of the C/C++ console application.

image.png

To make sure we always build the C# .NET library assembly before we build the C/C++ console application, we set up the C# library as the dependency project of the C/C++ console application.

image.png

Now we have the whole solution looks like the following screen-shot.

image.png

Running the C/C++ console application, we got the resulting screen shot as shown below as expected.

image.png

Calling Convention of __stdcall

We have been using __cdecl calling convention from the very beginning, you might be wondering what if you want to use __stdcall calling convention after all, a lot of C style DLL do use __stdcall (WINAPI) calling convention.

Firstly, we will need to change the C# code for the Calculator class.

As you see the above code, we change the calling convention to CallingConvention.StdCall, but this is not enough, for __stdcall calling convention, Visual C++ compiler requires its function export name mangled as shown below.

The number at the end of any mangled name represents the number of bytes of all the parameters of the C-style export function, for example, for Add function, it has 2 integer parameter, so it is 8 bytes, the managed name is Add@8.

For the C/C++ console application side, what we only need to do is to decorate the external C function with __stdcall.

So far, we have been able to change the calling convention of the C-style function exported by the C# assembly by using Unmanaged Exports. This is very useful while we are getting deep into the power of Unmanaged Exports. You will see the point in the later section.

You probably have guessed already, this technique only works on .NET static methods. It is a limit. You might also continue thinking that any instance method of a class can’t be exported this way and further we won’t be able to call them. This is not true. let’s continue the journey of discovering new power of Unmanaged Export Technique.

Calling C# .NET Class and its instance methods from Native C/C++

As you see the previous example of the C# class of Calculator, all its 4 methods are static in order for us to export them. Considering we have a Calculator class with all instance methods as shown below.

Since all the methods are not static methods, we can not decorate them with [DllExport] attribute any more. If you try to, UnmanagedExports task will output error message like the following,

Export error EXP0016: MethodDeclarationParserAction: The method is not static. Only static methods can be exported.

The work-around of overcoming this limit is to write a wrapper class using static methods to bridge to the instance methods.

Let’s create a new class named CalculatorWrapper to wrap all the instance methods of the class, Calculator.

As you probably found out already, there is new method we added and new parameter we added to each method, those are 2 points we should talk about.

  1. We added a new static method to create the C# calculator instance as shown below. Just like in C#, we must create an instance of Calculator in the native C++ before we can call the instance methods. A wrapper method must be provided to native C++ to accomplish this requirement.

2. We added a new parameter named calculator as type of object marshaled as UnmanagedType.IUnknown. This is also a must because whenever we invoke any instance method, we will need to pass the instance of the Calculator from the native C/C++ code. From .NET to C#, any object can be marshaled as UnmanagedType.IUnknown.

On the native C/C++ side, since we are using IUnknown interface, we will need to include Windows.h header file.

And we also have 5 external functions to reference.

Since we are programming OOP, we would want to wrap everything in a class named Calculator so that we create a bridge class(wrapper class) completely simulate the .NET version of calculator class.

It is amazing that we now have a native C++ proxy class of the .NET version of Calculator class. Let’s put it in action then.

The result is of course similar to the result earlier when we use static methods in the Calculator class.

image.png

So far so good, we are able to call the instance methods of a calculator class in .NET from the native C/C++ code. But I am still not happy with this implementation because we still have to write a C++ proxy class. In the real world of native C++, if someone provides you a C++ DLL containing a class of Calculator, you would expect the following declaration in its header file, right?

Okay, then. Let’s introduce more complicated stuff to continue this topic.

Exporting C++ Class from .NET assembly just like Visual C++

The topic is kind of scaring, any one would probably think, it is a hack, isn’t it? Well, I would think every advanced coding technique is probably a hack anyway, and most of the stuff I am writing here is well documented, that it why I am able to write it, right? And I can explain why it is supposed to work in every detail, and mostly importantly, it just works.

Let me firstly present you the modified version of C# code of CalculatorWrapper and then I will explain the code in details. I am writing the code myself while I am writing this article, it does not mean it is simple, it is complicated and time-consuming, but it is a fundamental knowledge of how we can make it work.

There are a few points I would like to explain.

Calling Convention

Since we are simulating native C++ instance function calls, the calling convention can no longer be __stadcall or __cdecl, it must be __thiscall, which means for x86 CPU architecture, the this pointer is passed in the CX register.

Export Function Name

The export function name must be mangled name since it is C++ function instead of C-style function. The mangled names carry the exact name function declaration for Visual C++ compiler to find the right export function.

Constructor and Destructor

We do not want to write bridge proxy class in the native C++ any more, we completely rely on the C# .NET side to do the work, this make the native C++ interface quite neat and simple. The destructor is implemented in the C# side as well.

Structure of the C++ Class

Although we do not have to implement the proxy class in the native C++ code, but the class does exist. The size of the proxy class is 8 bytes for x86 CPU architecture. The first 4 bytes(a pointer) is for the virtual function table, the second 4 bytes (a pointer) is the storage of IUnknown pointer. And yes, you can guess, for x64 CPU architecture, the size shall be 16 bytes.

After we built the new C# assembly and open the mixed mode DLL, we can see the export functions and their mangled names exactly you would see in a traditional native C++ DLL exporting classes.

image.png

From the native C++ side, we will write the declaration for the C++ bridge class of Calculator as shown below.

As you see, the declaration of the native C++ proxy class Calculator defines the size of the proxy class, we do not implement the proxy class any more. We declare the destructor as virtual for the purpose of inheritance. We can discuss this in the future.

And this is the old method of testing the Calculator class, and you should have noticed I did not change anything except the words of the message.

The result is still same as before.

image.png

Create a Complete Mini Native C++ Library with Inheritance

In the preceding examples, we were able to discover that we can create native C++ library by using mangled names to export static function when using unmanaged exports, and the resulting library works exactly like those created by Visual C++. But I found there are 2 issues I have not addressed.

Virtual Function Table

The virtual function table is not required, there is no need to make any native C++ function virtual because any native C++ class is just proxy class, the .NET corresponding class will handle the the function routing. Without the virtual function table, each proxy class will have only a size of 4 bytes to store the IUnknown pointer from the .NET for x86 and 8 bytes for x64.

Inheritance

Inheritance must be implemented in order to simulate the similar object hierarchy of .NET system.

We are going to add and export the following classes so that we can create object in native C++ and output its full type name. The original calculator class still remains, but it is not our focus any more.

image.png

C# ObjectCppWrapper Class

C# TypeCppWrapper Class

C# StringCppWrapper Class

The complete native C++ code is listed as below.

image.png

There are a few points I would like to make,

Native object is always created in native C++

Instance of native C++ proxy class must be created in the native C++ code and pass to .NET and destroyed in the native C++ code as well. A proxy class instance always live in the native C++ world

String is bridged instead of being marshaled to const char

A .NET string can be copied to char only when it is required to, such as being printed out.

Function returning .NET object

When any .NET method returns an instance of a class, it must be returned to native C++ as the last parameter of the function by reference.

Limits of Unmanged Exports

Everything has limits, so does Unmanaged Exports.

  • We can only export static methods.
  • We have to set the platform target to either x86, ia64 or x64. AnyCPU assemblies cannot export functions.
  • We can’t put the exports in generic types or export generic methods. (The CLR wouldn’t know what type parameters to use)
  • It is not available for mono which does not support mixed mode very well and it does not support .export directive neither. It is not a cross-platform solution.
  • .NET exception can’t be caught in the native C/C++.

Adding Exception Handling

As we talked about in the preceding section, there is no exception handling when using Unmanaged Exports, but there is a work-around technique with which we can pass an instance of .NET exception back to the native C++ which then creates a native C++ exception based on the .NET exception. By using this technique, we can use try and catch at the native C++ side and do not have to worry about the exception on the .NET side since everything in the .NET exception can be passed back to the native C++ world, and we do not need to worry about anything related to stack unwind, etc. The .NET exception shall be caught on the .NET side in the wrapper method of the original method. I will give you example of how it works.

This is a simplified version of the Exception Handling Technique we implemented in xInterop Native C++ to .NET Bridge, a Native C++ Wrapper Generator for wrapping .NET assembly automatically.

The first thing we will have to do is to bridge the .NET class, Exception. For this, I created ExceptionCppWrapper class.

I only create two bridge methods for the class of Exception, getMessage for accessing Message property and getHResult for accessing HResult property respectively.

In an earlier example, we use a class name Calculator which contains a method of Divide, we ignored the fact Divide may throw exception in the .NET code when one value is divided by zero. We will need to change a little bit to that class. For simplicity, we are going to only change the method of Divide, which is only the method where exception may be raised.

The following is the enhanced version of CalculatorCppWrapper class.

If you pay attention, you may have found I added a new method as shown below once again. The method is wrapped in side of try catch block, so whatever exception occurs, it will be caught and the exception will be passed back to the native C++ code.

In the native C++, we also have to add a new bridge class to access the .NET exception class.

When such exception bridge class returns from any call from .NET world, I would like to transfer the information to a native C++ exception, so that we will be able to use C++ try catch block as well. So I derived a new class from std::exception.

Now, we need to go back to the native C++ bridge class of Calculator. We will need to add additional code in case exception occurs in the call to .NET from native C++.

When we receive a .NET exception from a bridge call to .NET, we will re-throw a native C++ exception by wrapping the information inside so that we can access the information all the way passed from .NET when an exception is caught.

 

Leave a Comment

Translate »