Like them or not, exceptions are the standard way of handling programming errors in C#. We need to be able to catch C# exceptions in our C++ code so we can gracefully recover from them. Likewise, we need uncaught C++ exceptions to behave like unhandled C# exceptions: display an error and move on instead of crashing. Today’s article continues the series by implementing both those features in the GitHub project and explaining how that implementation was done.

Table of Contents

Before we start to talk about exceptions, let’s have a quick followup to the poll in last week’s article. Thank you to everyone who took the time to fill it out, even if it was just a few seconds. If you still haven’t filled it out, it’s still open so you can still submit your opinions.

The poll results were very encouraging! The majority of you are interested in reading more about C++ scripting, so I’ll continue on starting with today’s article. A little over half of you would write at least some of your scripts in C++ and a few of you are even interested in collaborating on the GitHub project. If you answered that you want to collaborate on the project, please send me an e-mail, leave a comment, or otherwise get in contact with me. That said, let’s move on to exceptions…

Both C# and C++ support exceptions and they work very similarly. However, if C++ calls a C# function that throws an exception, the C# exception can’t be caught by C++. Just like other class instances in C#, exceptions can’t be simply passed between languages like we can with an integer. The internal implementation of the exceptions is also different, so the two languages are incompatible enough that we can’t simply throw in one language and catch in the other.

This means we’ll have to implement handling for catching and re-throwing both C# and C++ exceptions. Let’s start with the simpler one: C++ exceptions. Recall that C++ functions are invoked by C# for one of two reasons. First, when the plugin boots up the C++ Init function is called to set up the plugin and then call the game code’s PluginMain function. Second, MonoBehaviour “messages” like Update call into their corresponding C++ functions.

So there’s only two places we need to handle C++ exceptions. For starters, we’ll need to use try and catch just like in C#.

try
{
	PluginMain();
}
catch (Exception ex)
{
	// PluginMain threw an Exception
}
catch (...)
{
	// PluginMain threw something else
}

Now that we’ve caught the exception, we need to pass it to the C# side. As usual for class instances, we’ll need to pass an object handle to a C# function. So let’s start by defining the C# side:

// The exception that C++ threw
static Exception UnhandledCppException;
 
// A function for C++ to call to pass C# an exception
[MonoPInvokeCallback(typeof(SetExceptionDelegate))]
static void SetException(int handle)
{
	// Get the Exception corresponding to the handle
	// Store it in a static variable
	UnhandledCppException = ObjectStore.Get(handle) as Exception;
}

Now we can call this from the C++ side:

try
{
	PluginMain();
}
catch (Exception ex)
{
	// Pass the thrown exception to C#
	SetException(ex.Handle);
}
catch (...)
{
	// Create a generic exception and pass it to C#
	Exception ex("Unhandled exception calling PluginMain");
	SetException(ex.Handle);
}

The final piece is to have C# re-throw that exception. All we need to do is check if an exception was set to UnhandledCppException after the call to the C++ function and re-throw:

// Call a C++ function. In this case the boot function that calls PluginMain.
Init(/*params*/);
 
// Check if C++ called back during the function to set an unhandled exception
if (UnhandledCppException != null)
{
	// Make a local variable copy of the exception
	Exception ex = UnhandledCppException;
 
	// Clear the exception
	UnhandledCppException = null;
 
	// Re-throw the exception nested inside a helpful message
	throw new Exception("Unhandled C++ exception in Init", ex);
}

To try this out, assume there’s a throw in PluginMain:

throw Exception("boom");

Now this won’t crash the game as normal, but instead be caught and re-thrown on the C# side. As a result, we’ll see a message like this in the editor’s Console pane:

Exception: boom
Rethrow as Exception: Unhandled C++ exception in Init
NativeScript.Bindings.Open (Int32 maxManagedObjects) (at Assets/NativeScript/Bindings.cs:630)
NativeScript.BootScript.Awake () (at Assets/NativeScript/BootScript.cs:21)

This works no matter what kind of exception C++ threw. So that’s basically all there is to handling uncaught C++ exceptions.

Now for the more difficult topic: handling C# exceptions. The core concept is the same as with C++ exceptions: catch and re-throw. So let’s start by defining the C++ side:

// A global variable to hold the thrown C# exception
Exception unhandledCsharpException(nullptr);
 
DLLEXPORT void SetCsharpException(int32_t handle)
{
	// Clear any old exception
	delete unhandledCsharpException;
 
	// Store the new exception
	unhandledCsharpException = Exception(handle);
}

Then we add try and catch to all the C# functions that C++ calls. These include constructors, methods, properties, and even fields. For example, here’s the wrapper function for GameObject.GetTransform:

[MonoPInvokeCallback(typeof(UnityEngineGameObjectPropertyGetTransformDelegate))]
static int UnityEngineGameObjectPropertyGetTransform(int thisHandle)
{
	try
	{
		// Get "this" from the object handle
		var thiz = (GameObject)ObjectStore.Get(thisHandle);
 
		// Call the property "getter"
		var returnValue = thiz.transform;
 
		// Store the return value in ObjectStore
		// Return the handle it gives us
		return ObjectStore.GetHandle(returnValue);
	}
	catch (Exception ex)
	{
		// Store the exception in ObjectStore
		// Pass the object handle we get back to C++
		SetCsharpException(ObjectStore.Store(ex));
 
		// Still have to return something, so return the default
		return default(int);
	}
}

So by the time this wrapper function returns it will have called the C++ SetCsharpException function which will set the global unhandledCsharpException variable. That means we can modify the C++ code to check if unhandledCsharpException was set immediately after the C# function returns, just like we did before after C# called into a C++ function.

Transform GameObject::GetTransform()
{
	// Call the C# wrapper function
	auto returnValue = UnityEngineGameObjectPropertyGetTransform(Handle);
 
	// Check if C# set an exception during the call
	if (unhandledCsharpException)
	{
		// Copy the exception to a local variable
		Exception ex = unhandledCsharpException;
 
		// Clear the exception
		unhandledCsharpException = nullptr;
 
		// Re-throw the exception
		throw ex;
	}
 
	// Still have to return something
	// If there was no exception, this is the real return value
	// If there was an exception, this is a dummy value (e.g. the default)
	// It's OK because this line will never execute due to the above "throw"
	return returnValue;
}

The final step is to catch the C# exception in C++:

// Make a null GameObject
GameObject nullGo(nullptr);
 
try
{
	// Try to call the "transform" getter property
	nullGo.GetTransform();
}
catch (Exception ex)
{
	// Handle the exception by logging it
	Debug::Log(ex);
}

This actually works out really well. Catching C# exceptions is just as easy as catching them in C# code. All the complexities are handled behind the scenes so our game code can simply use the exceptions features. It’s even fine if we forget to catch a C# exception on the C++ side. To understand why, consider the following sequence:

  1. C# calls a C++ function like Init
  2. The C++ Init function calls PluginMain in a try-catch block
  3. PluginMain in C++ calls a C# function like the GameObject.transform “getter” property
  4. GameObject.transform throws an exception
  5. C# catches the exception and calls the C++ SetCsharpException function with it
  6. C++ re-throws the exception
  7. The catch block in Init for the call to PluginMain catches the exception and calls SetException on C#
  8. C# finishes the call to Init, checks its global exception variable, and re-throws it

So we’re really well covered here from both sides, even if we forget a catch in C++.

Still, there’s one remaining issue: C++ can only receive exceptions of type Exception. That means that even though the above code threw a NullReferenceException we still just get a plain Exception. There are two reasons for this.

First, SetCsharpException constructs an Exception object regardless of what type of exception the handle is for. So we need to work around this by defining more such SetCsharpException functions. We need one per type that the C++ code is interested in. So let’s add on to the code generator’s JSON file to specify just that:

{
	"Name": "UnityEngine.GameObject",
	"Properties": [
		{
			"Name": "transform",
			"GetExceptions": [
				"System.NullReferenceException"
			]
		}
	]
}

Here we’ve said explicitly that we want to handle NullReferenceException when calling GameObject.transform “getter”. The same goes for “setter” properties, constructors, and methods. Fields can only throw a NullReferenceException, so there’s no need to specify anything there.

Now that we have that specified, the code generator just needs to generate a few more bits of code. We’ll need some more catch blocks in C# code to handle each of these exception types. When those catch blocks are executed, they need to call a newly-generated function like SetCsharpException. Here’s how those look:

// C# now handles the specific exception type
// It passes the handle to a specific C++ function
[MonoPInvokeCallback(typeof(UnityEngineGameObjectPropertyGetTransformDelegate))]
static int UnityEngineGameObjectPropertyGetTransform(int thisHandle)
{
	try
	{
		var thiz = (GameObject)ObjectStore.Get(thisHandle);
		var returnValue = thiz.transform;
		return ObjectStore.GetHandle(returnValue);
	}
	catch (NullReferenceException ex)
	{
		SetCsharpExceptionNullReferenceException(ObjectStore.Store(ex));
		return default(int);
	}
	catch (Exception ex)
	{
		SetCsharpException(ObjectStore.Store(ex));
		return default(int);
	}
}
// C++ function to set a specific type of function
DLLEXPORT void SetCsharpExceptionSystemNullReferenceException(int32_t handle)
{
	delete unhandledCsharpException;
	unhandledCsharpException = NullReferenceException(handle);
}

Now the correct type of exception is being set in C++, not just the base Exception.

However, this still won’t work due to the second reason as mentioned above. It turns out that throw in C++ uses static typing. So when the compiler sees a call to throw {variable of type Exception} it doesn’t apply polymorphism to figure out that the actual variable assigned to the Exception-type variable was a NullReferenceException. That means it won’t go to the right catch block even if there is one for a NullReferenceException.

To work around this, we need to employ a couple of tricks. First, we need to be able to use throw from a place where the correct type is known at compile time. A convenient location for this is within the exception type itself. So we can use a virtual function and implement it in each type of exception:

struct Object
{
	virtual void ThrowReferenceToThis()
	{
		// Throws an Object
		throw *this;
	}
};
 
struct NullReferenceException : Object
{
	virtual void ThrowReferenceToThis()
	{
		// Throws a NullReferenceException
		throw *this;
	}
}

Now instead of re-throwing with throw unhandledCsharpException, we call ThrowReferenceToThis.

One final wrinkle is that we need the global variable to be a pointer, largely because the size of derived exceptions like NullReferenceException is potentially larger than just Exception so a non-pointer might not leave room for all its fields. So we’ll change that and use the new and delete operators when setting and accessing the global exception variable.

In the end we have a global variable like this:

Exception* unhandledCsharpException = nullptr;

SetCsharpException functions that look like this:

DLLEXPORT void SetCsharpExceptionSystemNullReferenceException(int32_t handle)
{
	delete unhandledCsharpException;
	unhandledCsharpException = new NullReferenceException(handle);
}

But there’s even one more wrinkle to iron out! The ThrowReferenceToThis function doesn’t exist in C#. We want to add it on in C++ only to help us throw exceptions. The code generator could be modified to add a special case for exception types, but there’s another route. Instead, we can generate a new derived type for each specifically-handled type of exception. That derived type can implement ThrowReferenceToThis and the game code will still catch the right exception types due to polymorphism. Here’s how one of these exception types looks:

struct NullReferenceExceptionThrower : NullReferenceException
{
	NullReferenceExceptionThrower(int32_t handle)
		: NullReferenceException(handle)
	{
	}
 
	virtual void ThrowReferenceToThis()
	{
		throw *this;
	}
};

Now we just need to modify the SetCsharpException functions to use these “thrower” types:

DLLEXPORT void SetCsharpExceptionSystemNullReferenceException(int32_t handle)
{
	delete unhandledCsharpException;
	unhandledCsharpException = new NullReferenceExceptionThrower(handle);
}

Finally, the code that handles these exceptions like this:

if (unhandledCsharpException)
{
	// Copy the exception variable to a local variable
	Exception* ex = unhandledCsharpException;
 
	// Clear out the exception variable
	unhandledCsharpException = nullptr;
 
	// Use the virtual function to re-throw the right type of exception
	ex->ThrowReferenceToThis();
 
	// Delete the exception now that we're done with it
	delete ex;
}

The game code that uses this is unchanged. It still looks just like C# code, including the list of catch blocks that can now receive specific types of exceptions:

// Make a null GameObject
GameObject nullGo(nullptr);
 
try
{
	// Try to call the "transform" getter property
	nullGo.GetTransform();
}
catch (NullReferenceException ex)
{
	// Handle the exception by logging a specific message
	Debug::Log("NRE using the transform getter");
}
catch (Exception ex)
{
	// Handle the exception by logging it
	Debug::Log(ex);
}

That about wraps up our support for exceptions. With this included we can now easily handle C# exceptions in C++ code and C++ exceptions in C# code. All the types are correct and everything works very similarly regardless of the language you’re programming in. The complexity is neatly tucked away in the bindings that the code generator outputs.

Reminder: if you’re interested in collaborating on this project, please contact me by e-mail or comment. Also, there’s still time to fill out the poll about this series if you haven’t done so already. Thanks!