Skip to content

Working with Mono objects

RoqueDeicide edited this page Jan 2, 2015 · 1 revision

mono::object

IMonoInterface.h contains a namespace "mono" where a number of typedefs is defined. The main one is object typedef and it's used for pointers to Mono objects located in managed memory, those pointers are direct equivalents of System.Object instances.

mono::object pointers are used wherever System.Object would be needed in Mono API and when creating method signatures in C++ that match ones in C# and vice versa.

Bear in mind that the only difference between mono::object and System.Object is that the former is not tracked by Mono GC which means such point is only guaranteed to be valid until garbage collection starts.

Notice a very important list of typedefs that follow mono::object one: they are all equivalents of mono::object, however they allow to tell the developers what that object is expected to be. For instance when using mono::vector3 the object is expected to be a boxed 3D vector.

Mono handles

In order to make accessing Mono objects safer and to be able to access Mono object API you should wrap the pointer with IMonoHandle wrapper object. There are 3 varieties of such wrappers:

  1. Free - does no extra actions when created, which means: it's quick, allows you to access the API but is quite unsafe.
  2. Persistent - registers wrapped pointer with GCHandle, which protects the object from being GCed in case there are no more references to it in managed code. However accessing API with such handle is rather slow, since every time you do it, the wrapper must acquire a valid reference to use. Use this handle if you need to store the reference for very long time, but you don't access it frequently.
  3. Pinned - upon creation, instructs GC to pin given object in place allowing safety of persistent handle with access quickness of free one. The only drawback is that every pinned object creates problems whenever garbage collection happens, and those problems can pile up with many GCs.

Fields

Accessing a field is done differently depending on what object we are dealing with:

  1. Boxed value-type object - there is no API for accessing fields of such objects, you have to unbox it and then access the field with conventional C++ syntax.
  2. Managed object - use Get/SetField method. There usage is slightly different, depending on whether you get the value or set it. When getting the value of the field that is of value-type, you gonna get a reference to the boxed instance of it. When setting the value, you must provide a pointer to the object you want to use as a new value.

Sample code:

//
// Access fields of value-type object.
//
mono::vector3 boxedVector = GetVec3();		// Some function that gives us the boxed vector.
// We cannot get its X field with any API. So lets unbox it.
Vec3 unboxedVector = Unbox<Vec3>(boxedVector);
// Now we can access X field!
float xComponent = unboxedVector.x;
//
// Get the 32-bit integer field of managed object.
//
mono::object someObject = GetSomeObject();
// We need a wrapper to be able to access Mono object API.
IMonoHandle *someObjectWrapper = MonoEnv->WrapObject(someObject, true, false);		// Persistent, but not pinned.
// Here is the boxed value of the field "IntField".
mono::int32 boxedInteger = someObjectWrapper->GetField("IntField");
// We have to unbox the value to use it.
int unboxedInteger = Unbox<int>(boxedInteger);
//
// Set the 32-bit integer field of managed object.
//
int newValue = 10;		// This variable will contain the value to set the field to.
// Lets set the field.
someObjectWrapper->SetField("IntField", &newValue);		// We need to provide a pointer to the new value, that is why we created that variable.

Properties

Getting and setting properties is essentially the same as with fields, with the only difference being that you can do that with value-type objects, however you have to have them unboxed and you will need IMonoClass API instead of IMonoHandle.

Methods

You can call methods of the any object by using IMonoHandle::CallMethod function:

// Create an array of arguments. Lets say, our function accepts one integer and one string.
IMonoArray *args = MonoEnv->CreateArray(2, false);
// Fill the array with values.
args->At<mono::int32>(0) = Box(10);
args->At<mono::string>(1) = ToMonoString("Some text");
// Now invoke the function. The overload of the function will be chosen based on types of arguments.
someObjectWrapper->CallMethod("SomeMethod", args);

Release

When working with persistent and pinned handles, you need to manually inform Mono when you don't need the object anymore. You can do that by simply calling IMonoHandle::Release function.