by Miguel de Icaza (miguel@ximian.com), Paolo Molaro (lupus@ximian.com)
This document describes how to embed the Mono runtime in your application, and how to invoke managed methods from C, and how to invoke C code from managed code.
For a general overview of why you would like to embed Mono in your application see the Scripting With Mono article.
Table of Contents
Source Code
Source code and samples for Mono embedding can be found in the Mono distribution in the mono/samples/embed directory.
How Embedding Works
Typically you would begin with an existing C application:
Embedding links the mono runtime with your application, so your application now has a full virtual execution system running side-by-side with it. This is done by linking `libmono' with your application (we will cover the details about this later). Once linked, the address space of your application would look like this:
The Mono embedded API exposes the Mono Runtime to the existing C code. The interface exposed by the Mono runtime lets the developer control various aspects of the runtime and inspect the code that runs on the CIL world inside the Mono runtime.
Once you have the Mono runtime initialized, the most interesting thing to do is to load some CIL/.NET code into it. The code can be written in any of the Mono supported languages like C#, Java, IronPython or Visual Basic. This will result in an address space like this:
The C code is typically referred as unmanaged code, while the CIL code generated by a CIL-compiler is referred to as managed code.
But to make the system more interesting than just load some managed code and have it run side-by-side with your code, it is desirable to have the managed code invoke C code, and the C code invoke managed code.
Managed code can invoke unmanaged code in two ways, using P/Invoke or using the low-level Mono embedding API.
The result looks like this:
[Image:exposing.png] Now your existing C code can trigger methods in the managed world, and the managed world can react and notify of any interesting changes to the C code:
Missing image
Callback.png
Image:callback.png
The loaded assembly can be as simple as you want, some common things that developers have done:
- Load a library of methods that are wired to the user interface of an application: GUI elements, dialog boxes are then handled on the managed world, while core processing remains in C.
- Load user defined code as assemblies, and trigger invocations of those from the existing C code base.
- Move some of the development to the managed world, gaining all of the benefits of managed development (exception handling, runtime type checking, just-in-time compilation, rich introspection system, type-safe libraries and more) while keeping your existing investment in C intact.
- A launcher that launches background threads to perform some work on behalf of the application.
- Embed a web application or a web service server inside your application.
- Use Mono to host the user scripting interface, turning Mono and the scripts into a generic plugin interface.
- Integrate the Mono object system with third-party object systems.
Since the Mono framework is a fairly powerful framework there are almost no limitations on the different kind of applications that you can create with the above setup.
Embedding the Runtime
Embedding the runtime consists of various steps:
- Compiling and linking the Mono runtime
- Initializing the Mono runtime
- Optionally expose C code to the C#/CIL universe.
These are discussed in detail next.
Compiling and Linking
To embed the runtime, you have to link your code against the Mono runtime libraries. To do this, you want to pass the flags returned by pkg-config to your compiler:
$ pkg-config --cflags --libs mono
is used to get the flags for the JIT runtime.
Like this:
$ gcc sample.c `pkg-config --cflags --libs mono`
You can separate the compilation flags from the linking flags, for instance, you can use the following macros in your makefile:
CFLAGS=`pkg-config --cflags mono` LDFLAGS=`pkg-config --libs mono`
On windows you need to generate an import library for mono.dll by getting the following file:
http://anonsvn.mono-project.com/viewcvs/*checkout*/trunk/mono/msvc/mono.def
and creating mono.lib with the command:
lib /nologo /def:mono.def /out:mono.lib /machine:x86
Then you link your application with mono.lib.
Initializing the Mono runtime
To initialize the JIT runtime, call mono_jit_init, like this:
#include <glib/glib.h> #include <mono/jit/jit.h> #include <mono/metadata/assembly.h> MonoDomain *domain; domain = mono_jit_init (file_name);
That will return a MonoDomain where your code will be executed. file_name is the name of the main assembly file: this file is used to determine which runtime version to load (1.1 or 2.0), depending if the file was compiled for the 1.1 or 2.0 runtime versions.
The first thing you usually do is to load your assembly and execute it:
MonoAssembly *assembly; assembly = mono_domain_assembly_open (domain, "file.exe"); if (!assembly) error ();
In the above example, the contents of `file.exe' will be loaded into the domain. This only loads the code, but it will not execute anything yet. You can replace `file.exe' with another transport file, like `file.dll'.
To start executing code, you must invoke a method in the assembly, or if you have provided a static Main method (an entry point), you can use the convenience function:
retval = mono_jit_exec (domain, assembly, argc - 1, argv + 1);
If you want to invoke a different method, look at the `Invoking Methods in the CIL universe' section.
Certain features of the runtime like Dll remapping depend on a configuration file, to load the configuration file, just add:
mono_config_parse (NULL);
Which will load the Mono configuration file (typically /etc/mono/config), but if you want to load your own configuration file, pass the filename as the argument to mono_config_parse:
mono_config_parse ("my_mappings");
Configuring the Runtime
When Mono is embedded into an application it needs a way of finding its runtime assemblies and configuration files. By default it will use the system defined locations that the runtime was built with (typically assemblies in /usr/lib/mono and configuration in /etc/mono). This will work out of the box for you.
But if you are using a Mono that was relocated from an original distribution, for example if you are distributing your application with Mono, you must inform the Mono runtime where to find its assemblies and configuration files. To do so, you must call the mono_set_dirs routine:
mono_set_dirs (myapp_lib, myapp_etc);
Shutting down the runtime
To shutdown the Mono runtime, you have to clean up all the domains that were created, use this function:
mono_jit_cleanup (domain);
Exposing C code to the CIL universe
The Mono runtime provides two mechanisms to expose C code to the CIL universe: internal calls and native C code. Internal calls are tightly integrated with the runtime, and have the least overhead, as they use the same data types that the runtime uses.
The other option is to use the Platform Invoke (P/Invoke) to call C code from the CIL universe, using the standard P/Invoke mechanisms.
To use the P/Invoke system, you have to make your C function public, for example:
void DoSomething () { /* ... */ }
To make the runtime lookup the symbol in the current executable, use the special library name __Internal like this, in your DllImport attribute:
using System.Runtime.InteropServices; [DllImport ("__Internal", EntryPoint="DoSomething")] static extern void DoSomething ();
The "__Internal" library name will instruct Mono not to look this up in an external library, but to try to satisfy the symbol referenced (DoSomething) in the current executable image.
The P/Invoke framework provides extensive marshalling capabilities (converting strings, converting data types, mapping delegates to function pointers and much more). This is the simplest mechanism to use.
If you want more performance and want to avoid the overhead that P/Invoke adds you can register C code directly with the runtime, and later bind to it from managed code.
To register an internal call, use this call in the C code:
mono_add_internal_call ("Hello::Sample", sample);
Now, you need to declare this on the C# side:
using System;
using System.Runtime.CompilerServices;
class Hello {
[MethodImplAttribute(MethodImplOptions.InternalCall)]
extern static string Sample ();
}
Since this routine returns a string, here is the C definition:
static MonoString*
Sample ()
{
return mono_string_new (mono_domain_get (), "Hello!");
}
Notice that we have to return a `MonoString', and we use the `mono_string_new' API call to obtain this from a string.
Internal calls are very close to the runtime and have very little overhead but lack some of the sugar that P/Invoking to the "__Internal" library offers.
Windows Considerations
On Windows, it is necessary for you to flag any methods that you want to expose through P/Invoke to be flagged with __dllexport or __declspec(dllexport).
Invoking Methods in the CIL universe
Calling a method in the CIL universe from C requires a number of steps:
- Obtaining the MonoMethod handle to the method.
- The method invocation.
To get a MonoMethod there are several ways.
You can get a MonoClass (the structure representing a type) using:
MonoImage *
mono_assembly_get_image (MonoAssembly *assembly);
MonoClass *
mono_class_from_name (MonoImage *image, const char* name_space, const char *name);
and then loop in the returned class method array until you get the one you're looking for. There are examples of such searches as static functions in several C files in metadata/*.c: we need to expose one through the API and remove the duplicates.
The other, simpler, way is to use the functions in debug-helpers.h: there are examples of their use in monograph, mint and the jit as well. You basically use a string description of the method, like:
"System.Object:GetHashCode()"
and create a MonoMethodDesc out of it with:
#include <mono/metadata/debug-helpers.h> MonoMethodDesc* mono_method_desc_new (const char *name, gboolean include_namespace);
You can then use:
MonoMethod* mono_method_desc_search_in_class (MonoMethodDesc *desc, MonoClass *klass); MonoMethod* mono_method_desc_search_in_image (MonoMethodDesc *desc, MonoImage *image);
and
#include <mono/metadata/assembly.h> MonoImage *mono_assembly_get_image (MonoAssembly *assembly);
to search for the method in a class or in an image. You would typically do this just once at the start of the program and store the result for reuse somewhere.
Invoking a Method
There are two functions to call a managed method:
MonoObject* mono_runtime_invoke (MonoMethod *method, void *obj, void **params, MonoObject **exc); and MonoObject* mono_runtime_invoke_array (MonoMethod *method, void *obj, MonoArray *params, MonoObject **exc);
obj is the 'this' pointer, it should be NULL for static methods, a MonoObject* for object instances and a pointer to the value type for value types.
The params array contains the arguments to the method with the same convention: MonoObject* pointers for object instances and pointers to the value type otherwise. The _invoke_array variant takes a C# object[] as the params argument (MonoArray *params): in this case the value types are boxed inside the respective reference representation.
From unmanaged code you'll usually use the mono_runtime_invoke() variant.
Note that this function doesn't handle virtual methods for you, it will exec the exact method you pass: we still need to expose a function to lookup the derived class implementation of a virtual method (there are examples of this in the code, though).
You can pass NULL as the exc argument if you don't want to catch exceptions, otherwise, *exc will be set to the exception thrown, if any. if an exception is thrown, you can't use the MonoObject* result from the function.
If the method returns a value type, it is boxed in an object.
For example, to invoke the following C# methods:
class MyClass { static void Foo (int value) { ... } int Bar (string name) { ... } }
assuming you got the corresponding MonoMethod* in foo_method and bar_method and this_arg is a MonoObject* of type MyClass, you simply execute:
/* we execute methods that take one argument */ void *args [1]; int val = 10; /* Note we put the address of the value type in the args array */ args [0] = &val; /* execute Foo (10); * it's a static method, so use NULL as the second argument. */ mono_runtime_invoke (foo_method, NULL, args, NULL); /* a string is a reference, so we put it directly in the args array */ args [0] = mono_string_new (domain, "Hello"); /* execute my_class_instance.Bar ("Hello"); * See the Creating Objects section to learn how to get this_arg. */ MonoObject *result = mono_runtime_invoke (bar_method, this_arg, args, NULL); /* we always get a MonoObject* from mono_runtime_invoke (), so to get * the integer value we need to unbox (which returns a pointer to * the value stored in the object) and dereference. */ int int_result = *(int*)mono_object_unbox (result);
Creating objects
Creating an object involves two separate actions: allocating the memory and invoking the constructor.
For constructors that take no arguments this is very simple:
/* we usually get the class we need during initialization */ MonoImage *image = mono_assembly_get_image (assembly); MonoClass *my_class = mono_class_from_name (image, "MyNamespace", "MyClass"); ... /* allocate memory for the object */ MonoObject *my_class_instance = mono_object_new (domain, my_class); /* execute the default argument-less constructor */ mono_runtime_object_init (my_class_instance);
For more complex constructors or if you want to have more control of the execution of the constructor, you can use mono_runtime_invoke() as explained in the previous section, after getting the MonoMethod* representing the constructor:
/* execute my_class_instance = new MyClass ("Mono rocks"); */ MonoObject *my_class_instance = mono_object_new (domain, my_class); void *args [1]; args [0] = mono_string_new (domain, "Mono rocks"); /* constructor methods return void, so we ignore the return value, * the constructed object is my_class_instance. */ mono_runtime_invoke (ctor_method, my_class_instance, args, NULL);
Data types
Unlike PInvoke, there is no intermediate layer that translates the managed types into unmanaged typed or the other way around. With the embedded runtime, when you register an internal call, or when you call a method, you need to use the data types expected by the runtime.
This means that you need to convert your C types into Mono runtime types before you can pass them to Mono, or you must convert those Mono types to C types before you can consume them"
| C Type | Mono Type | C to Mono | Mono to C |
|---|---|---|---|
| strings, char * | MonoString * | mono_string_new, mono_string_new_len,
mono_string_new_wrapper, mono_string-new_utf16 | mono_string_to_utf8, mono_string_to_utf16 |
| array of x | MonoArray * | mono_array_new, mono_array_new_full, mono_array_new_specific |
See the embedded API documentation for more details about these.
Unmanaged to Managed Thunks
With Mono 2.0 we introduced a new function that can wrap a MonoMethod into a function pointer:
void* mono_method_get_unmanaged_thunk (MonoMethod *method);
You'll be able to store the returned pointer in a function pointer with the proper signature and call that directly from C:
typedef gint32 (*GetHashCode) (MonoObject *obj); GetHashCode func = mono_method_get_unmanaged_thunk (System_Object_GetHashCode_method); gint32 hashvalue = func (myobject);
Pre-2.0 notes: It may not be possible to manage exceptions in that case, though. I need to think more about it.
Threading issues
If your application creates threads on its own, and you want them to be able to call code into the CIL universe with Mono, you have to register the thread with Mono before issuing the call.
To do so, call the mono_thread_attach() function before you execute any managed code from the thread
Signal handling
Mono consumes a set of signals during execution that your applications will not be able to consume, here is what these are:
- SIGPWR, SIGXCPU: these are used internally by the GC and pthreads.
- SIGFPE: caught so we can turn that into an exception
- SIGQUIT, SIGKILL to produce ExecutionEngineException.
- SIGSEGV: to produce NullReferenceExceptions
One signal picked at startup time between SIGRTMIN and SIGRTMAX. The signal is picked up by finding a signal in that range which is set to SIG_DFL.
Optionally:
- SIGUSR2: this is used when --trace=disable is passed on the command line and its used to turn on/off the output of trace.
Currently Mono does not provide a mechanism for signal chaining, but one might be available in the future, see Bug #75990 (http://bugzilla.ximian.com/show_bug.cgi?id=75990) for information on the current status of this feature.
Common Problems
Threads
If your applications has threads that will access Mono, access Mono variables, point to Mono objects, be called back by Mono, these threads must be registered with the Mono runtime using the mono_thread_attach.
Samples
See the sample programs in mono/samples/embed (http://anonsvn.mono-project.com/viewvc/trunk/mono/samples/embed/) for examples of embedding the Mono runtime in your application.





