Project Mono: C++to C# pointer passing in Linux

Hey there,

today’s assignment is to pass C++ object pointers to a C# dll and back, while running in Linux. What? Linux? Yes! You read it right! By using the Mono runtime, you are able to use C# libraries from a C++ program, passing objects & callbacks, setting & retrieving data and so on. The only thing you need is embedding Mono (its logo is shown above and it depicts a gorilla,yeap), C++ and your C# dll, and that is all. Mono will allow you to control the way that your C++ program will interact with the dll, by calling different exposed dll functions from your C++ program.

Why to exchange data between C++ and C#?

Software reusability could be one reason. Imagine that you have a C# application that implements a basic GUI and an engine, which is responsible for all calculations that are performed by the program, based on the user input. At some point, you figure that your application’s performance could be better and that some of your calculations could be optimized. After optimizing your C# code, you decide that your program could be more performant if it is implemented in a more lower level language, such as C and C++. Although, you wouldn’t like to implement both the GUI and the engine subsystems from scratch. Indeed, the GUI could be reused while the engine could be reimplemented in C++. You could wrap all your GUI operations in a DLL, implement the engine in C++, glue Mono between them in order to exchange information and you are ready to go!

The availability of a specific library could be very limited to only some platforms/languages and that platform (C#, Windows) is not your project’s native (C++,Linux). Instead of reinventing the whole functionality in C++ & Linux, why not just fetch the C# dll, wrap it up in another C# dll (that you will write) and then expose all the API/exported functions to your C++ programs/environment? This is a way better solution, allowing you to:

  • reuse code,
  • treat the C# dll functionalities as a black box,
  • limits the amount of new code to just code that sets up Mono & performs calls through Mono.

Assignment Description

Create a C++ program in Linux that passes a C++ object pointer (Container*) to a C# method, which fills up the object with data that were produced in the C# side. So basically, you will need something like this:

Untitled Diagram (1)

One nice question that could come in mind is, why is there a shared library (*.so) and not just one executable that calls Mono-runtime and the DLL, why do we need the shared library in the middle? Because we decided that only the shared library implements the actual store data function, which also is exposed to the DLL. After calculating the data, the DLL calls the shared library’s exported store function, by passing the pointer that was received during the initial Mono call from the shared library to the DLL. The C# side does not know the actual data structure implementation of the container that was passed to it, actually it doesn’t know that an actual object pointer was passed into it at the first place. Why is that? Because we decided to pass the pointer as an unsigned int of 64 bits (uint64_t)! The C# side just needs to call the exposed stored data function, pass the pointer-integer and the data into the C++ function. The exposed C++ static function will convert the integer back to pointer (Container*), add the data in the container and return. After the initial call of the C++ shared library function to the C# dll, the container object in the C++ side is filled up with the data that the C# side just generated. Sounds interesting & challenging enough? Let’s see how it can be done!

Requirements

Windows

Linux

  • C++ 11
  • Mono runtime: apt-get install mono-devel
  • Makefile

 

Demo Time

1. The DLL (C# .Net side)

Our DLL contains one class, the class Program that contains three functions:

  • superCalculation, the actual functionality that we would like to reuse from C++ through Mono to C#
  • storeResults, the function that is exposed through our C++ shared library and knows how to restore the actual C++ object pointer and store the data that we received from a call to the superCalculation method
  • process, the entry point method that is called from the C++ side and triggers the calculation and the data storing scheme
using System;
using System.Collections.Generic;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;

namespace TestDLL
{
    class Program
    {
        public static List<int> superCalculation()
        {
            //Actual name of the function should be: "dateNowToIntegerList"

            List<int> result = new List<int>();
            string dateString = DateTime.Now.ToString();
            for(int i = 0;i<dateString.Length;i++)
            {
                if (Char.IsNumber(dateString[i]) == false) continue;
                result.Add(Int32.Parse( dateString[i].ToString() ));
            }

            return result;
        }

        [DllImport("./Kazelib.so", EntryPoint = "storeResults")]
        public extern static void storeResults(System.UInt64 container, int value);
        
        [DllExport("process", CallingConvention = CallingConvention.Cdecl)]
        public static void process(System.UInt64 container)
        {
            List<int> results = superCalculation();
            for (int i = 0; i < results.Count; i++)
            {
                storeResults(container, results[i]);
            }
        }
    }
}

 

By using the attribute DllImport, you allow the C# compiler to know that at some point, a storeResults method implementation will be available to the DLL from somewhere outside the DLL. By setting the name of the shared library and the entrypoint string, you map the method implementation & a method name symbol  with an external library, allowing the compiler to know where exactly to find the implementation of the storeResults method, that is inside the shared library “Kazelib.so”, in the entry point method “storeResults” of the shared library.

 On the other hand, by using the attribute DLLExport before the declaration of the method process, you inform the compiler that this method should be publicly exposed to the outside world, allowing other programs that load the TestDLL to call the process method from the outside. The attribute DLLExport is available through the RGiesecke module that we fetched earlier through NuGet.

After creating a new project in Visual Studio that contains the code above, build it. You will get a nicely packed dll. Now copy the dll to your Linux box, in a new directory, where our Mono project will live.

2. The shared library (C++ Linux)

Our shared library consists of one header file (Kazelib.h) and one implementation file (Kazelib.cpp).

2.1 The header (Kazelib.h)

The header contains the implementation of the Container class, which is the object that will store the results that we retrieved by calling Mono and C#.

#include <vector>
#include <cstdint>
#include <iostream>

class Container {
    private:
        std::vector<int> m_Values;
    public:
        Container(){}
        
        void storeValue(int value)
        {
        	m_Values.push_back(value); 
        }

        void dump()
        {
        	for(auto &i : m_Values)
        	{
        		std::cout << i << std::endl;
        	}
        }
};

//Entry point for C++
extern "C" void process(void);

//Used by C# to store the results
extern "C" void storeResults(uint64_t container,int value);

The header exposes two functions, the process and the storeResults public functions. The first will be exposed to our client program to call; the user program after loading Kazelib.so, will call the function process in order to trigger the whole function call sequence. The storeResults function is exposed to the C# universe, allowing us to call it from the C# side. As parameters, storeResults will accept the container object pointer-integer and an integer value that will be stored in the instance of the container object.

2.2 The implementation (Kazelib.cpp)

The logic written in the implementation file knows how the Container object looks like (it includes Kazelib.h) and knows how to set up Mono and invoke the exposed C# method in order to fill the Container object. Kazelib’s entry point is the function process, which is publicly exposed for anyone that would like to use Kazelib.so. A call to the function will call the private function processThroughMono that will perform the following steps:

  • initialize Mono runtime by loading our dll by name
  • load the appropriate assembly from the dll
  • traverse through the symbols of the assembly by scope, in order to find the MonoMethod object process located in namespace <TestDLL>, class <program>, method name <process> ( the “TestDLL.Program:process” string)
  • instantiate a Container object
  • reinterpret cast it to 64-bit unsigned integer
  • fill the container as void* argument that will be passed to the C# side
  • call process through Mono
  • close Mono runtime

Tip: during a lifetime of a program, the function mono_jit_init should be called only once, this means that you cannot turn Mono on and turn off and then turn on etc. It is allowed only once, meaning that a second call in the same process will fail.

#include "Kazelib.h"
#include <mono/jit/jit.h>
#include <mono/metadata/assembly.h>
#include <assert.h>
#include <mono/metadata/debug-helpers.h>

static void processThroughMono(std::string& dll)
{    
    //Initialize mono runtime
    MonoDomain* domain = mono_jit_init (dll.c_str());
    MonoAssembly *assembly;

    assembly = mono_domain_assembly_open (domain, dll.c_str());
    assert(assembly);

    MonoImage *image =  mono_assembly_get_image  (assembly);
    assert(image);
    MonoMethodDesc* desc = mono_method_desc_new ("TestDLL.Program:process", true);
    assert(desc);

    MonoMethod* method = mono_method_desc_search_in_image (desc, image);
    assert(method);

    //Create our container
    Container* c = new Container();

    //Cast to uint64
    uint64_t ptr = reinterpret_cast<uint64_t>(c);

    //Fill it as an argument before the mono method invokation
    void* args[1];
    args[0] = &ptr;

    //Invoke C# code
    mono_runtime_invoke (method, nullptr, args, nullptr);

    //Clean mono runtime
    mono_jit_cleanup (domain);

    //We did it!
    c->dump();

}

void process()
{
    std::cout << "process()!!" << std::endl;
    std::string dll("test.dll");
    processThroughMono(dll);
}

void storeResults(uint64_t container,int value)
{
    Container* c = reinterpret_cast<Container*>(container);
    c->storeValue(value);
}

The implementation of the exposed function to the C# universe (storeResults) casts the integer back to an object and then stores the integer value that was passed to the object. The DLL will call it as many times it needs to fill all integer results from the superCalculation C# call.

3. C++ Client Code (main.cpp)

The client code loads (dlopen & dlsym) the shared library by name (process) and then triggers the whole function call sequence by calling the shared library’s process function. Then it unloads (dlclose) the shared library and returns.

#include <iostream>
#include <assert.h>
#include <dlfcn.h>

bool process(std::string &lib, std::string &function)
{
    void *handle;
    void (*process)(void);
    char *error;

    handle = dlopen (lib.c_str(), RTLD_LAZY);

    if (!handle) {
        fputs (dlerror(), stderr);
        return false;
    }

    process = (void (*)(void)) dlsym(handle, function.c_str());
    if ((error = dlerror()) != nullptr)  {
        fputs(error, stderr);
        return false;
    }
    process();
    
    dlclose(handle);
    return true;
}

int main(int argc, char** argv)
{
    std::string lib("./Kazelib.so");
    std::string function("process");
    assert ( process(lib, function) );
}

4. Compile (Makefile)

The Makefile consists of the following commands:

all:
g++ -std=c++11 -Wall -fPIC -O2 -c Kazelib.cpp Kazelib.h  `pkg-config –cflags –libs mono-2`
g++ -std=c++11 -shared -o Kazelib.so Kazelib.o   `pkg-config –cflags –libs mono-2`
g++ -std=c++11 main.cpp -ldl

The “-ldl” switch is used in order to link our executable against the dynamic linking library, allowing the compiler to find the symbols of dlopen, dlsym and dlclose. The `pkg-config –cflags –libs mono-2` part of the command allows us to find & link against lib Mono’s runtime, allowing us to use any Mono symbol.

5. Run

gclkaze@tzertzelos:~/Desktop/Projects/Tzertzelos/Mono$ make all
g++ -std=c++11 -Wall -fPIC -O2 -c Kazelib.cpp Kazelib.h  `pkg-config –cflags –libs mono-2`
g++ -std=c++11 -shared -o Kazelib.so Kazelib.o   `pkg-config –cflags –libs mono-2`
g++ -std=c++11 main.cpp -ldl
gclkaze@tzertzelos:~/Desktop/Projects/Tzertzelos/Mono$ ./a.out
process()!!
5
1
5
2
0
1
6
1
1
0
7
2
5
gclkaze@tzertzelos:~/Desktop/Projects/Tzertzelos/Mono$

 

Yes, we did it! We were able to pass our new container C++ object through Mono to C#, fill it with data & print its contents in the C++ side by calling the dumps method call. If you may have noticed, the actual “super calculation” performed in the C# side consist of the conversion of the current date time during execution into an integer list, thus, every time you execute the client program, the Container object will be filled with different numbers. It seems that the previous screenshot was taken at exactly 15th of May 2016,11:07:23.

Summary of what we have learned

I hope i gave you an idea about the following:

  • what is Mono & how can we use it in Linux
  • load shared library and call a function from it
  • compile shared library in Linux
  • the DLLImport & DLLExport attributes
  • cool usage of reinterpret cast
  • export functions from C++ to C#
  • export functions from C# to C++
  • how to pass pointers from C++ to C# and back to C++

In the Linx section below, you will find a github link that points to the actual repo that contains all source files that were shown during this demo.

As you read, this post was quite colossal in terms of information and tried to keep it short as much as possible. Based upon all of this information, i could easily think and write about at least 5 articles for different subjects encountered in this post. I tried to focus in the high-level goal and its sequence of high-level steps and not in the lower-level ones, such as detailed information about “MonoImage” and “MonoDesc” objects. I am sure you will find what you need by clicking the attached links.

Now, go & reuse some code by using Mono in Linux!

Kaze

Linx

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s