Embedding with Mono
Prerequisites
- Scripting Module Structure
Article
With the basic structure of our scripting system laid out, we will now take a moment to talk about integrating Mono with your C++ project generally before we step into our integration.
First, Mono's documentation on embedding is a great place to start. We'll re-hash the important concepts here, so it's not required reading, but if you have questions I would look there first. The second resource is the Mono Github repository - especially the metadata files, which is where you'll find most of the interfaces we will be working with.
Let's get started...
Integrating Mono and C# for game engine scripting can be thought of in 4 distinct parts:
- C# library - we'll build a library of C# classes and files that contain external function references, which act as callbacks to C++ functions in our engine. This library will largely mirror our internal C++ classes and functions - for example, we'll have an InputSystem class, a ResourceSystem class, a Component base class that can be extended, and built-in components such as CameraComponent.
- Wrapper methods around our C++ functions to translate from Mono-specific types to our internal engine objects and data types. Most plain old data (POD) types have direct translations: a uint in C# will be an unsigned int in C++; a float or double in C# will be a float or double in C++. All strings will be passed in as MonoString pointers, and all object references passed into our C++ code will be MonoObject pointers. From there, we can use Mono functions and the C# reflection system to determine which C++ class type they are. Each function we want to expose to Mono will require a wrapper method; for example, the Rotate function of our Transform that has a C++ signature of
void Rotate(vector3 axis, float degrees)
will have a wrapper method with a signature ofvoid Rotate(MonoObject* vectorAxis, float degrees)
and we'll use our ScriptSystem to translate from MonoObject to UObjects and UVariants. - C# methods to call from C++ - as we saw in our Implementing Scripting overview, a C# class or script will have a standard set of functions that we want to call at certain times: Initialize on creation, Update or FixedUpdate in the game loop, and Destroy when the component is removed. This will require the ability to call C# functions from C++, which Mono makes fairly straightforward and robust with function pointers.
- Reflection of C# classes - leveraging C#'s reflection system, we can get a listing of all class types in a library that inherit from a base class (for example, extending the Component, System, or Script classes) and we can expose those in the editor to allow our game developers to attach those types to objects in the game world. Further, we can get a listing of variables and their data types and we can also expose those in our editor to give developers and artists more control.
All of these parts together give us a really deep bidirectional integration from our engine into our game logic: game developers will leverage our C# class library, which will call internal C++ functions through wrapper functions to modify our engine state; our engine will call C# functions like Update, written by game developers, inside the game loop, which will also use our C# library to access game state and engine internals; and, leveraging C#'s reflection capabilities, we can expose classes and variables to our game designers, artists, and developers in the editor so they can modify the experience in real time without needing to recompile code.
Let's go over each of these in detail and talk about how we will interact with Mono from C++ to accomplish our goals.
C# Class Library
For every class and function in our engine, we can create a corresponding class and set of functions on the C# side. If this sounds like a lot of work, it is! But you get the hang of things pretty quickly, and being able to do some work in C# using some of the returned values from C++ functions creates a pretty powerful integration.
Here is the sample class from the Mono website on embedding:
using System;
using System.Runtime.CompilerServices;
class Hello {
[MethodImplAttribute(MethodImplOptions.InternalCall)]
extern static string Sample ();
}
This creates a class called Hello in C# that contains one (static) function called Sample that takes no parameters and returns a string. The attribute and the extern keyword tell Mono that this will be an internal callback.
Let's take a look at a more robust example from our engine, the Transform class:
using System;
using System.Runtime.CompilerServices;
namespace UEngine
{
public partial class UTransform
{
/**
* Move from current position (add to position)
*/
[MethodImpl(MethodImplOptions.InternalCall)]
public extern void Move(Vector3 amount);
/**
* Rotate around an axis
*/
[MethodImpl(MethodImplOptions.InternalCall)]
public extern void Rotate(Vector3 axis, float degrees);
/**
* Scale by a percentage
*/
[MethodImpl(MethodImplOptions.InternalCall)]
public extern void Scale(Vector3 amount);
public Vector3 forward
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}
public Vector3 up
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}
public Vector3 right
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}
public Vector3 position
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
[MethodImpl(MethodImplOptions.InternalCall)]
set;
}
public Vector3 scaling
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
[MethodImpl(MethodImplOptions.InternalCall)]
set;
}
public Quaternion rotation
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
[MethodImpl(MethodImplOptions.InternalCall)]
set;
}
}
}
You'll notice that this class largely mirrors the functions and properties of our C++ UTransform class. You'll also notice that you can also make getter and setter functions callbacks! That means we can pass back the current state of variables and use them as variables in C#. For example, setting a Transform's absolute position can be done in C# with transform.position = new Vector3(0, 0, 0)
. Another nice feature of C# is implementing "read-only" properties that only have a getter - you can see that here for our forward, up, and right vectors. The last difference from the Mono example is that as a best practice, we will wrap everything in the UEngine namespace for engine-related integrations. Even though we generally prefix all of the classes here with "U", doing so will avoid type confusion later on for our game developers.
From here, we can start to inherit and implement other classes, for example, a UComponent class that has a UEntity (we'll also need to write a C# UEntity class) property called "gameObject" (mirroring Unity parlance for those who are familiar):
using System;
using System.Runtime.CompilerServices;
namespace UEngine
{
public partial class UComponent
{
// Game object we are currently attached to
public UEntity gameObject
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}
}
}
Or a RenderComponent base class that inherits from UComponent and has a transform (as all rendered components can have a local offset from the root) that is read-only as well:
namespace UEngine
{
public partial class URenderComponent : UComponent
{
public UTransform transform
{
[MethodImpl(MethodImplOptions.InternalCall)]
get;
}
}
}
We'll go into more depth on implementation of these classes and functions in the next part, but you can see that we will largely mirror our engine classes. For each function and class we want to expose to the C# world, we'll need to write a C# class, even if the implementations are in C++.
C++ Wrapper Functions
With some C# classes written, we now need to write the C++ implementation or wrapper functions that will connect our Mono and C# methods with our internal engine functions.
Mono will pass in most plain old data types - int, float, double, bool, etc. from C# to your C++ functions. Strings will be pass as a MonoString pointer, while objects will be passed in as MonoObject pointers. We'll need to use some of Mono's built in functions and our ScriptSystem to use these.
We connect a C++ function to the C# equivalent using a function called mono_add_internal_call
. This takes a character string signature of the function and a function pointer as parameters.
Here is our C# class and function from the Mono documentation:
using System;
using System.Runtime.CompilerServices;
class Hello {
[MethodImplAttribute(MethodImplOptions.InternalCall)]
extern static string Sample ();
}
And here is our C++ function that we will connect:
MonoString* Sample() {
return(mono_string_new(mono_domain_get(), "Hello!"));
}
As you can see, it returns a MonoString pointer, which gets translated into a C# string. This simplified example does not take any parameters - first, because the C# function doesn't have any parameters, and second because it is static. For all non-static C# functions, Mono will pass a MonoObject pointer as the first parameter to the C++ function, specifying which object the function is being called on.
Finally, we connect these two functions together by calling mono_add_internal_call
:
mono_add_internal_call ("Hello::Sample", Sample);
Here, the signature is specified as: "[namespace].[class]::[function] (parameters)". It is very important for the function signature to match, otherwise you'll get weird errors about the library being out of date. We did not include a namespace in the C# file, so we also don't include one in the signature. Finally, parameters are optional - I recommend leaving them out unless you have two or more functions with the same name and different parameters, then you'll have to specify them.
In the next articles, we'll dive into more detailed and complicated functions, but these examples show us how to call C++ functionality from C#. Now let's look at how to call C# functionality from C++.
Calling C# methods from C++
There are two primary ways to call C# methods from C++. The first is using the mono_runtime_invoke
function, which takes a function, object pointer, and parameter list of arguments and optionally outputs any exceptions. While easy to get started with, this option is not the most performant. Since performance of a scripting language is always a concern, Mono offers a second option called managed to unmanaged thunks, which are essentially function pointers with Mono signatures.
In our engine and editor, the primary way that developers will write scripts is by deriving from a C# UMonoScriptComponent class that has 4 primary functions: Initialize, Update, FixedUpdate, and Destroy. Here's what that class looks like in C#:
using System;
using System.Runtime.CompilerServices;
namespace UEngine
{
public partial class UMonoScriptComponent : UComponent
{
// Declare our overridable functions
public virtual void Initialize() { }
public virtual void Update(float delta) { }
public virtual void FixedUpdate(float delta) { }
public virtual void Destroy() { }
}
}
Since we already know the signature of those methods, we can easily create C++ equivalents. For each function pointer, Mono will pass in at least 2 parameters: the first parameter will always be the MonoObject pointer to the C# object this function is being called on, and the last parameter will always be a MonoException* pointer that will be used as an output for any unhandled exceptions that occur inside the function. For the Update and FixedUpdate functions, we'll have a middle parameter that is the floating point delta time between calls:
typedef void (*UMonoScriptInitFunction)(MonoObject*, MonoException**);
typedef void (*UMonoScriptUpdateFunction)(MonoObject*, float, MonoException**);
typedef void (*UMonoScriptFixedUpdateFunction)(MonoObject*, float, MonoException**);
typedef void (*UMonoScriptDestroyFunction)(MonoObject*, MonoException**);
With our function pointer types, we can now ask Mono to give us pointers to the class methods. Here is an example script that we will return to throughout this series that handles player input:
using UEngine;
public class PlayerInput : UMonoScriptComponent
{
// Movement speed
[SerializeField]
public float moveSpeed = 15.0f;
// Speed to turn
[SerializeField]
public float turnSpeed = 50.0f;
// Internal components
private UCameraComponent camera;
// Declare our overridable functions
public override void Initialize() {
camera = gameObject.GetComponent<UCameraComponent>();
}
public override void Update(float delta) {
float moveAmount = UInputSystem.GetActionState("move forward");
float turnAmount = UInputSystem.GetActionState("turn");
camera.transform.Move(camera.transform.forward * moveAmount * moveSpeed * delta);
camera.transform.Rotate(camera.transform.up, turnAmount * delta * turnSpeed);
}
}
Ignoring most of the details for now, you can see that we have a class called PlayerInput that derives from UMonoScriptClass and re-implements the Initialize and Update functions. We'll cover iterating over class types in a library in a future article, but let's pretend for now that we know a class called "PlayerInput" exists in a C# library called "MyGame.dll". Mono provides functions that allow us to retrieve pointers to classes and methods inside of classes by name:
// Load assembly
MonoAssembly* assembly = mono_domain_assembly_open(mono_domain_get(), "MyGame.dll");
MonoImage* image = mono_assembly_get_image(assembly);
// Get PlayerInput class
MonoClass* playerInputClass = mono_class_from_name(image, "PlayerInput");
// Get Mono method pointers
MonoMethod* initMethod = mono_class_get_method_from_name(playerInputClass, "Initialize", 0);
MonoMethod* updateMethod = mono_class_get_method_from_name(playerInputClass, "Update", 1);
There is a bunch of new stuff here, but the first two lines simply load a compiled DLL file (called an assembly) and the image, which is the part of the DLL file that contains the CIL code - the interpreted byte-code generated by .NET. The next line finds the PlayerInput class inside of the image, and the last two lines get the Mono equivalent of function pointers to the Initialize and Update methods of the PlayerInput class.
Translating the Mono function pointers to C++ function pointers is then pretty straightforward:
UMonoScriptInitFunction initFunc = (UMonoScriptInitFunction)mono_method_get_unmanaged_thunk(initMethod);
UMonoScriptUpdateFunction updateFunc = (UMonoScriptUpdateFunction)mono_method_get_unmanaged_thunk(updateMethod);
Our initFunc
and updateFunc
function pointers will not call C# code when executed.
Given a MonoClass pointer, we can also create new objects in the C# universe by calling mono_object_new
to create an object, and mono_runtime_object_init
to call it's constructor. Let's do that for a new PlayerInput object now:
MonoObject* playerInputObject = mono_object_new(mono_domain_get(), playerInputClass);
mono_runtime_object_init(playerInputObject);
Tying this together with our new function pointers, we can now call the Initialize and Update functions of our C# class on our PlayerInput object from C++:
initFunc(playerInputObject, NULL);
float delta = 0.1f;
updateFunc(playerInputObject, delta, NULL);
Combined with our first two parts, we can now call C++ code from C# and C# code from C++. Finally, we'll look at Mono's reflection capabilities to look at inheritance, attributes, and variables.
Leveraging C# reflection
In addition to calling and creating functions and classes, we can also get more information about a class, function, or variable by using C#'s reflection system and Mono functions.
In particular, we would like to know two things: which classes are inherited from UMonoScriptComponent (and will therefore implement our Initialize, Update and other script component functions), and which variables in those classes are public and the developer has chose to expose to the designers, artists, and other gameplay programmers in the editor.
The first part (inheritance) is fairly straightforward. Mono even provides a handy function for this called mono_class_is_subclass_of
into which we can pass the MonoClass pointers to a potential child and parent class we want to check:
if(mono_class_is_subclass_of(childClass, parentClass, false)) {
// This class (childClass) inherits from parentClass
}
The second part is a bit more complicated, but definitely more interesting... if you've used the Unity engine before, you've likely seen their SerializeField attribute that looks like this:
[SerializeField]
public float movementSpeed = 10.0f;
This example is slightly contrived because Unity automatically serializes public variables, so the attribute is not necessary here, but it demonstrates the "look and feel". In our engine, we'll make the [SerializeField] attribute required for all fields that the developer wants to expose in the editor.
Attributes in C# are also classes and their constructors dictate how the attribute is used. In this case, we'll add a SerializeField class to our engine C# library that looks like this:
using System;
using System.Runtime.CompilerServices;
namespace UEngine
{
[System.AttributeUsage(System.AttributeTargets.Field)]
public class SerializeField : System.Attribute
{
public SerializeField() { }
}
}
Our class inherits from System.Attribute, which means it can be used as an attribute. We also specify an attribute on our attribute (attribute-ception) that specifies that it can be used on fields (instead of on classes or functions). Finally, our constructor takes no parameters, so we can just use it as [SerializeField]
. We could also create a variation that does take parameters - for example, a version that specifies the field name to show in the editor:
public SerializeField(string fieldName) { ... }
You could then use the SerializeField attribute like this:
[SerializeField("Player Move Speed")]
public float movementSpeed = 10.0f;
Neat!
As we are examining classes in a loaded library, we can also look at the fields that a class has by calling mono_class_get_fields
. This code will iterate over all of the fields in a MonoClass and get the field name:
// Create an iterator
void* iter = NULL;
// Loop over the fields
MonoClassField* field = mono_class_get_fields(_class, &iter);
while (field != NULL) {
// Get the field name
std::string fieldName = mono_field_get_name(field);
// Move on to the next field
field = mono_class_get_fields(_class, &iter);
}
On top of that, we can find out more information about the field, like whether it is public or private, and what type of field it is.
Checking the accessibility of a field is a bit strange, since Mono gives us a function called mono_field_get_flags
, but doesn't actually put the FIELDATTRIBUTE* definitions or other flags in their header files that ship with Mono, so we need to redefine those ourselves. We can check the accessibility of a MonoClassField like so:
#define MONO_FIELD_ATTRIBUTE_ACCESS_MASK 0x0007
#define MONO_FIELD_ATTRIBUTE_PRIVATE 0x0001
#define MONO_FIELD_ATTRIBUTE_PUBLIC 0x0006
// Check field accessibility
uint32_t flags = mono_field_get_flags(field) & MONO_FIELD_ATTRIBUTE_ACCESS_MASK;
if ((flags & MONO_FIELD_ATTRIBUTE_PUBLIC) == true) {
// This is a public field
}
else {
// This is a private field
}
Finally, we can check the field type by calling mono_field_get_type
, which will return a MONOTYPE* from the MonoTypeEnum (MONO_TYPE_CLASS, MONO_TYPE_STRING, etc.):
MonoType* fieldType = mono_field_get_type(field);
int enumType = mono_type_get_type(fieldType);
switch (enumType) {
case MONO_TYPE_STRING:
// String type
break;
case MONO_TYPE_CLASS:
MonoClass* classType = mono_type_get_class(fieldType);
// Do some stuff here
break;
default: break;
}
Conclusion
All of this together gives us a pretty tight integration from C# to C++ and vice versa. We get to take advantage of some nice C# features and our game developers get to use an easy to use, modern language and drastically reduce the time it takes to write games.
This article provides an overview of how we'll integrate Mono and how you could do the same in your engine. In the following articles, we'll tie our first article together, covering engine classes and general structure, with the Mono details found here.