Geeks With Blogs
Timo Heinäpurola

C++ is a very powerful language. Well written native C++ code can perform much better than managed languages like C# and Java due to optimizations that the managed systems are not able to perform during run-time compilation (if this is done at all, that is). This is great for developers who work on gaming technology for instance. For people concerned with game logic, performance isn’t necessarily priority number one, but productivity and the ability to express oneself without too much head banging.

When implementing game logic most of the code expresses certain operations that need to be performed. Usually this means initiating an operation and waiting. The actual execution of the operation usually involves starting and updating animations, allocating and loading resources as well as doing rendering. These operations should be implemented in optimized code to get the most out of the platform but the control logic can be implemented in a language that facilitates higher productivity.

Because of this, game engine core functionality is usually implemented in C/C++ and a scripting layer is built to utilize those platforms facilities. Some major game engines implement their own scripting languages with Unreal Engine being the most notable example. These days there are a few options worth considering before starting down that path, however. A few examples include LUA, AngelScript and C# on Mono.

Mono is the system that I’m going to discuss in this post. There are many benefits to using Mono. First of all Mono provides basically all of the benefits of .NET/Java code. The most notable of these being productivity. Hardly anyone can argue that writing C# code with a good IDE like Visual Studio 2010 is not productive and developers can benefit from the same tools that business software developers get to use.

The second major benefit over many other scripting languages is performance. Yes, I did talk about game logic not having to be as optimized as platform operations but it doesn’t hurt if it is. Mono actually implements a JIT compilation process similar to the Microsoft .NET framework. At run-time it compiles the IL code generated by the Visual Studio compiler and emits machine code executable by the processor. This makes it very fast but not necessarily quite as fast as C/C++, mind you.

As a side note, for the doubtful ones, the performance of C++ stems from a couple of major aspects unique to native programming. First of all, you have full control over the memory you’re accessing. Because modern processors rely heavily on preloading blocks of memory into multiple on-chip caches, knowing what memory you’re accessing becomes very important. Also, modern processor architectures exhibit NUMA (non-uniform memory architecture), which means having your memory close to where it’s being used (processor/core wise) helps you get an additional boost. There are other factors as well but this article is not about C++ performance.

Going back to Mono, multi-platform support is also one of the major benefits of using Mono. Mono is written in such a way that it’s possible to compile it for most popular platforms and since it’s open source and if some platform is not supported, you can always switch into do-it-yourself gear. Perhaps the platforms of most interest to my readers are Windows, Linux, Android, XBox 360, PlayStation 3, Nintendo Wii and iPhone. If your favorite platform is not listed it doesn’t mean it’s not supported Smile

 

I’d like to explain to you, the reader, how to get started with embedding Mono to gain productivity for logic and still retain the effectiveness of native programming.

Compiling and initializing

Personally the most interesting scenario for using Mono is embedding it in a C/C++. This allows for building an interface on top of a C/C++ application to enable higher productivity while moving optimized code to the native host. In this blog post I will be using Mono on Windows.

To embed Mono in your C/C++ application you must first generate an import library using the module definition file found here. By the way, I see they have finally added mono_domain_create_appdomain, that we’ll be using later on, to the definition Winking smile

 

The exact command for generating the import library is as follows:

lib /nologo /def:mono.def /out:mono.lib /machine:x86

The command generates a mono.lib file that your application can link against. Once the import library is generated you will have to call mono_jit_init to initialize the Mono run-time and mono_jit_cleanup to cleanup. The function takes as its only parameter an assembly that will be loaded into the created application domain. It’s also possible to call mono_jit_init_version, which allows for initializing a specific version of the Mono run-time.

My personal recommendation is not to use this application domain for basically anything. This is because dumping the main application domain, i.e. ripping down the run-time, and then re-initializing it crashed the application. I’m not sure if this is fixed yet, however. A better option is to create a dummy assembly that will be loaded into this dummy application domain. This domain will only function as a default application domain that enables Mono to function correctly.

You can create this new application domain by calling mono_domain_create_appdomain (before calling this function ensure that the main dummy application domain is activated by calling mono_domain_set). This will create a new isolated virtual process execution environment with it’s own heap and stack. This means that application domains can be dropped at any time thus releasing assemblies loaded into the application domain for modifications.

As I already mentioned an application domain defines an isolated virtual process execution environment that has it’s own memory space. This means that you can load assemblies (managed DLL files) into an application domain and run code in it, letting it allocate objects as it wishes, and then drop the application domain, thus freeing all the allocated memory and the references to the loaded assemblies it owns.

Creating a new application domain for running your script code, for instance, is important, because of the fact that you can unload scripting components without having to restart the application.  You might ask why not just unload the assemblies that are not required anymore? The answer is short: because you can’t. The .NET framework, and thus Mono also, forbids unloading assemblies. This is due to the complicated interdependency of objects and their types. This might result in missing dependencies in the middle of application execution, which is hardly a good thing.

This brings us to what we do at Raccoon Interactive. We use an application domain to host the game script environment that is created to wrap an existing game environment inside our editor. If we make any changes to the built script assemblies we simply stop the game execution within the editor at which point the editor restores the state of the game to what it was before jumping into the game and dumps the application domain. After this we can simply jump into the game again and see the new modified scripts in action.

Loading and running code

To actually run your code in the application domain you have just defined you must first load the assembly into the domain. To load the assembly into the domain do the following.

MonoAssembly *pAssembly = mono_domain_assembly_open( pDomain,
    “MyAssembly.dll” );
if ( pAssembly == NULL )
    return NULL;
MonoImage *pImage = mono_assembly_get_image( pAssembly );

The mono_assembly_get_image function gets the assembly image required by many Mono operation, like reflection (getting type information).

Assemblies can be reflected for the type information they contain. You can also create objects once you know their types and call arbitrary methods to get your game code running. To get this type information you can call a couple of functions, my favorite being mono_class_from_name. This function takes the image of the assembly, the namespace of the class and the name of the actual class as parameters. The following is an example of getting a type.

MonoClass *pClass = mono_class_from_name( pImage,
            "MyNamespace", "MyClass" );

Great! Now we have loaded an assembly and we have a pointer to the class that we’re interested in. Mono also allows creating objects using the type information we have just requested and invoking methods on that object (or invoking static methods, for that matter).

To create an object and invoke a method on it, do the following.

// Get the constructor of the class.
MonoMethod *pConstructorMethod = mono_class_get_method_from_name_flags( pClass,
    ".ctor", -1, METHOD_ATTRIBUTE_SPECIAL_NAME );

// Create a new instance of the class.
MonoObject *pObject = mono_object_new( pDomain, pClass );

// Acquire a GC handle if the object will not be rooted in the CLR universe.
guint32 GCHandle = mono_gchandle_new( pObject, false );

// Invoke the constructor.
MonoObject *pException = NULL;
mono_runtime_invoke( pConstructorMethod, pObject, NULL, &pException );

The most important thing to note here is that the object is actually not initialized straight away as we create a new instance of the class. We first have to get the constructor with the quite peculiar name and manually invoke it. Here are are using a default constructor that does not take any arguments.

Also, note that we are acquiring a GC handle to the object. If you wish to use the object from native code and not have it rooted anywhere in the CLR universe (the Common Language Run-time universe) you must acquire a GC handle so that the garbage collector will not steal the object from you.

Once you are finished running your code you should call mono_domain_unload to unload the application domain that houses your code. You can also call this function to release a domain without actually quitting the application.

Next, I’ll cover manipulating fields of objects and going deeper into invoking methods and parameter passing.

Manipulating fields and invoking methods

Often, when working with script objects, you are interested in directly modifying an object in the script universe. You might, for instance have a native pointer in the object that defines a native resource to which the object is bound and you wish to initialize that directly. Mono provides a set of functions for doing just this.

When using Mono reflection, you must always get information about the certain type of primitive. For instance, to invoke methods, you must first get method information, as I showed in the previous section. To manipulate fields you must do the following.

MonoClassField *pField = mono_class_get_field_from_name( pClass,
    "_myField" );

You pass the method the class information and the name of the field. Simple, eh? Setting field values is a bit more complicated and requires some information about argument passing in the Mono run-time. Documentation on conventions is pretty much non-existent (as is all but simple initialization).

To se the value of a field of the type float you must do the following.

gfloat value = 1.0f;
gfloat *pValue[1];
pValue[0] = &value;
MonoException *pEx = NULL; mono_field_set_value( pField, pObject, (
void **)pValue, &pEx );

Not that pretty, is it? mono_field_set_value takes as parameters the field that you are setting, the object that contains the field, a pointer to a pointer to data and a pointer to a pointer to an exception. The idea behind the third parameter is that it collects the argument data from a pointer array that points to the data. You should always use type names prefixed with the character g (their part of GLIB). This will assure that the internal representation correctly maps to your data.

To set a field to reference a Mono object, do the following.

MonoObject *pObject = ...
gpointer pValue[1];
pValue[0] = pObject;

mono_field_set_value( pField, pObject, (void **)pValue, &pEx );

As before, we are passing an array of pointers where we now point to a single Mono object. To set properties, just use the mono_property_set_value function on a MonoProperty instance returned by mono_class_get_property_from_name, for instance.

Conclusions

In this article we covered first of all why using a managed layer on top of your native code is sometimes quite useful. On the technical side we covered initializing Mono, creating objects, manipulating them and calling methods on them. There are a lot of specifics that I have not covered and will get into them in future posts. These include boxing and registering internal call methods that enable managed code to call back into native code.

As always, if there are errors in this article or something does not work, please don’t hesitate to drop a comment and I’ll try to answer your questions to the best of my abilities!

Until next time!

Posted on Thursday, September 22, 2011 4:09 PM | Back to top


Comments on this post: Scripting with Mono

No comments posted yet.
Your comment:
 (will show your gravatar)


Copyright © raccoon_tim | Powered by: GeeksWithBlogs.net