DLLs—like SWCs in Flash—are an extremely handy way to build your code into reusable modules. Unfortunately, Unity has some quirks that can lead to crashes on iOS and other environments that don’t support JIT compilation. The biggest problem that crops up is when you try to use C# events in a DLL. Today’s article investigates why and where the problem occurs and presents a simple solution to work around the problem. Read on to learn how to safely use C# events in Unity DLLs!

Let’s start with a very simple usage of C# events to illustrate the problem:

public class Normal
{
	// Declare a delegate indicating the function signature
	// of callbacks called by the event
	public delegate void SomethingHandler();
 
	// Create the event with a default no-op handler
	public event SomethingHandler OnSomething = () => {};
 
	private void DispatchTheEvent()
	{
		// Dispatch the event
		OnSomething();
	}
}

When you run a Unity app on iOS or another non-JIT platform and use this event, you’ll get an error like this:

ExecutionEngineException: Attempting to JIT compile method
'(wrapper managed-to-native)
System.Threading.Interlocked:CompareExchange

To find out why, let’s compile this class to a DLL with Mono 3.12, which is the current version as of this writing. If you use MonoDevelop/Xamarin Studio, Mono is the compiler that’ll be used to compile your DLL. Once it’s compiled, .NET Reflector will decompile the DLL and show the functions for adding and removing callbacks to the event:

public void add_OnSomething(SomethingHandler value)
{
    SomethingHandler handler2;
    SomethingHandler onSomething = this.OnSomething;
    do
    {
        handler2 = onSomething;
        onSomething = Interlocked.CompareExchange<SomethingHandler>(ref this.OnSomething, (SomethingHandler) Delegate.Combine(handler2, value), onSomething);
    }
    while (onSomething != handler2);
}
 
public void remove_OnSomething(SomethingHandler value)
{
    SomethingHandler handler2;
    SomethingHandler onSomething = this.OnSomething;
    do
    {
        handler2 = onSomething;
        onSomething = Interlocked.CompareExchange<SomethingHandler>(ref this.OnSomething, (SomethingHandler) Delegate.Remove(handler2, value), onSomething);
    }
    while (onSomething != handler2);
}

As you can see, the code the compiler generated includes calls to the System.Threading.Interlocked.CompareExchange function which is attempting to JIT compile per the error message.

At this point it’s a good idea to wonder why this problem doesn’t happen in non-DLL code. To find out, put the same class in your Unity project’s Assets directory and then decompile the Library/ScriptAssemblies/Assembly-CSharp.dll that Unity compiles non-DLL project code to. Here’s what you’ll get:

[MethodImpl(MethodImplOptions.Synchronized)]
public void add_OnSomething(SomethingHandler value)
{
    this.OnSomething = (SomethingHandler) Delegate.Combine(this.OnSomething, value);
}
 
[MethodImpl(MethodImplOptions.Synchronized)]
public void remove_OnSomething(SomethingHandler value)
{
    this.OnSomething = (SomethingHandler) Delegate.Remove(this.OnSomething, value);
}

Here we see that Unity 4.6 generates entirely different code than Mono 3.12. This code doesn’t include calls to CompareExchange, so there’s no attempt to JIT and no error. Unfortunately, the code had to be moved out of the DLL to work around this problem. Since we still want to put the code in a DLL, let’s try another compiler: Microsoft Visual Studio 2013.

public void add_OnSomething(SomethingHandler value)
{
    SomethingHandler handler2;
    SomethingHandler onSomething = this.OnSomething;
    do
    {
        handler2 = onSomething;
        SomethingHandler handler3 = (SomethingHandler) Delegate.Combine(handler2, value);
        onSomething = Interlocked.CompareExchange<SomethingHandler>(ref this.OnSomething, handler3, handler2);
    }
    while (onSomething != handler2);
}
 
public void remove_OnSomething(SomethingHandler value)
{
    SomethingHandler handler2;
    SomethingHandler onSomething = this.OnSomething;
    do
    {
        handler2 = onSomething;
        SomethingHandler handler3 = (SomethingHandler) Delegate.Remove(handler2, value);
        onSomething = Interlocked.CompareExchange<SomethingHandler>(ref this.OnSomething, handler3, handler2);
    }
    while (onSomething != handler2);
}

This code is similar to the code produced by Mono 3.12, but slightly different. The key part is that it still uses CompareExchange and will therefore still have the JIT problem.

Given that both compilers are producing event adding and removing code for DLLs that triggers the JIT, let’s try writing our own code to add and remove. Here’s a workaround version:

public class Workaround
{
	// Declare a delegate indicating the function signature
	// of callbacks called by the event
	public delegate void SomethingHandler();
 
	// Create an instance of the delegate with a default no-op handler
	private SomethingHandler somethingInvoker = () => {};
 
	// Create the event with custom add and remove functions
	public event SomethingHandler OnSomething
	{
		// Add the listener to the delegate
		add { somethingInvoker += value; }
 
		// Remove the listener from the delegate
		remove { somethingInvoker -= value; }
	}
 
	private void DispatchTheEvent()
	{
		// Dispatch the delegate, not the event
		somethingInvoker();
	}
}

These add and remove functions are trivial, but defining them should stop the compiler from generating whatever implementation it wants. Let’s make sure that’s happening by decompiling each DLL. First, here’s the decompilation of the DLL produced by Mono 3.12:

public void add_OnSomething(SomethingHandler value)
{
    this.somethingInvoker = (SomethingHandler) Delegate.Combine(this.somethingInvoker, value);
}
 
public void remove_OnSomething(SomethingHandler value)
{
    this.somethingInvoker = (SomethingHandler) Delegate.Remove(this.somethingInvoker, value);
}

There are two things to notice here. First and most importantly, neither function uses CompareExchange anymore. Second, the code is now identical to the add and remove functions generated by the Unity 4.6 compiler. This means that we’ve effectively worked around the JIT issue with Mono 3.12. Next, let’s see if it worked for Visual Studio 2013 by decompiling that version:

public void add_OnSomething(SomethingHandler value)
{
    this.somethingInvoker = (SomethingHandler) Delegate.Combine(this.somethingInvoker, value);
}
 
public void remove_OnSomething(SomethingHandler value)
{
    this.somethingInvoker = (SomethingHandler) Delegate.Remove(this.somethingInvoker, value);
}

This version is exactly like the Mono 3.12 version and the Unity version, so it should also fix the JIT issue.

There are two downsides to the above approach though. The first is that it requires a fair amount more typing since you have to define your own add and remove functions and your own delegate. Here’s a comparison without the comments cluttering it up:

public class Normal
{
	public delegate void SomethingHandler();
	public event SomethingHandler OnSomething = () => {};
}
 
public class Workaround
{
	public delegate void SomethingHandler();
	private SomethingHandler somethingInvoker = () => {};
	public event SomethingHandler OnSomething
	{
		add { somethingInvoker += value; }
		remove { somethingInvoker -= value; }
	}
}

The second downside is that the add and remove functions no longer use the more-efficient Interlocked.CompareExchange function. That’s also the reason that the JIT issue occurred in the first place, but still a downside due to the loss of efficiency. However, this cost is only incurred when adding and removing events. This is normally much less common than actually dispatching events, so the overall impact to the app’s performance should be minimal.

This wraps up the investigation and workaround that allows our Unity apps to use C# events in DLLs. It’s a simple workaround that has minimal performance impact, but still an important one to ensure that the app works on non-JIT platforms like iOS. If you know of any other workaround for this issue, please let me know if the comments!