Integrating native iOS code into Unity

Ever wondered how you can add Unity support to your native iOS Framework? Here’s how…

We were working on an iOS advertisement framework project recently. Pretty straightforward on the surface — project was built in Swift and included Objective C support. It had a few methods that would display those advertisements and a few delegate methods that would notify the developer that the advertisement is going to show/hide.

But there was one requirement that made this project a lot more interesting — this framework needed to be usable in Unity. Developers could use this framework in their native iOS apps or their Unity games that would run as native iOS games (but could also run anywhere else without the adverts).

When you build a native iOS game, Unity creates a normal Xcode project for you, you can use it as you please. So the simplest way to integrate a framework into the project would be to instruct the developer to add it into the native code. But if you are familiar with Unity you probably already know that this isn’t the best option.

The other option is a bit more complicated. And that is to make the framework accessible to the Unity C# scripts. Sadly the official Unity documentation on this doesn’t give you too much information ( http://docs.unity3d.com/Manual/PluginsForIOS.html ).

All of this isn’t limited to a framework. It should be the same for any code that you need to run natively. But there are a few problems that you will only encounter when building a framework or a library. The examples below will show you how to make a simple framework that includes Unity integration. Examples will use Objective-C, but the same will work with Swift as well. You will just need to create a bridge between Swift and Objective-C since you can’t directly access swift code from Unity.


Calling native iOS methods from Unity

The first problem is simpler and already widely documented around the internet in guides like this.

Let’s create a new framework (that we will use for testing) and add a new CocoaTouch class that looks like this:

#import <Foundation/Foundation.h>
@protocol TestDelegate <NSObject>
- (void)newNumberAvailable:(int)number;
@end
@interface TestClass : NSObject
+ (void)displayFrameworkHello;
+ (void)displayFrameworkString:(NSString *)string;
+ (void)sendNumberToDelegate;
+(void)setDelegate:(id<TestDelegate>)delegate;
@end

The implementation is pretty simple as well:

#import “TestClass.h”
@implementation TestClass
id __delegate = nil;
+ (void)displayFrameworkHello {
NSLog(@”Hello from framework”);
}
+ (void)displayFrameworkString:(NSString *)string
{
NSLog(@”Message from framework: %@”, string);
}
+ (void)sendNumberToDelegate
{
if (__delegate && [__delegate respondsToSelector:@selector(newNumberAvailable:)]) {
[__delegate newNumberAvailable:10];
}
}
+(void)setDelegate:(id<TestDelegate>)delegate {
__delegate = delegate;
}
@end

This represents your current code that needs to communicate with Unity. It can do networking or anything else. In our case it just displays some messages and has a delegate that will receive number 10 when requested. So let’s create a unity bridge for it…

First create a new CocoaTouch class, name it UnityBridge, and open the header file. Delete everything in there and replace it with this:

#import “TestClass.h”
#ifdef __cplusplus
extern “C” {
#endif

void framework_hello();
void framework_message(const char* message);

#ifdef __cplusplus
}
#endif

Yes… what you are seeing here is C. We sadly need to use it for bridging. Try to make your bridge as simple as possible to prevent any complications.

Rename your UnityBridge.m file that was created to UnityBridge.mm . This is important as it will tell the compiler to compile it as Objective-C++ instead of Objective-C.

Delete everything from the .mm file and add the following code:

#import “UnityBridge.h”
void framework_hello() {
[TestClass displayFrameworkHello];
}
void framework_message(const char* message) {
[TestClass displayFrameworkString:[NSString stringWithUTF8String:message]];
}

This is just a simple bridge that calls the original framework methods and doesn’t really do anything fancy. We will deal with other methods in the second part of this article.

Your native code is now ready. All you have to do is Build it (Product > Build). Your .framework file will appear under Product folder in Xcode. Right click on the .framework file and select Show In Finder. You will need this file later.

Now open Unity. Create a new project, add some game objects (I suggest a plane and a cube). This project will represent your game. Now add a new script file to the Cube and call it CubeHandler. We will use this script to test our Unity integration.

Now for the important part. In your Assets folder create a new folder and call it Plugins. Inside that create a new folder and call it iOS. Now drag the .framework file from the previous step into this folder. This folder structure will ensure that the .framework will automatically be added to your iOS build. It will not be added to anything else (like Android builds).

The next step is to actually start using those framework methods that we created. Create a new C# script anywhere in your assets folder (except Plugins/iOS — don’t put it there). Let’s call it FrameworkBridge. Your folder structure should look something like:

Look at the Unity project after performing the necessary steps

And your FrameworkBridge file should look like this:

using UnityEngine; 
using System.Collections;
using System.Runtime.InteropServices;
using AOT;
public class FrameworkBridge : MonoBehaviour {      
#if UNITY_IOS
[DllImport ("__Internal")]
private static extern void framework_hello();

[DllImport ("__Internal")]
private static extern void framework_message (string message);
#endif
  public static void hello() {         
#if UNITY_IOS
if (Application.platform == RuntimePlatform.IPhonePlayer) {
framework_hello ();
}
#endif
}
  public static void message(string message) {         
#if UNITY_IOS
if (Application.platform == RuntimePlatform.IPhonePlayer) {
framework_message(message);
}
#endif
}
}

And that’s really pretty much all there is to it. You added two imports (InteropServices and ADT) that we will need (ADT is needed later). #if UNITY_IOS checks are there to ensure that this code only compiles when building for iOS, and the if (Application.platform == RuntimePlatform.IPhonePlayer) is there to ensure that this code actually only runs on iOS. The heart of this is in the first part with DllImport statements. In essence this imports the code from our external framework (more on this can be found in the Unity and Mono documentation). And in the next line we actually write the function prototypes that are located in our framework. They must have the same name, number of parameters, … The only difference is that we actually used string instead of const char* because Mono is smart enough to handle the conversion. Also notice the extern keyword. After that we use these functions as if they were native code.

Now go to the CubeHandler and try this code. Modify the start method so it looks like:

void Start () {
Debug.Log (“We are here”);
FrameworkBridge.hello ();
FrameworkBridge.message (“Hello from Unity!”);
}

You are ready to build the project now. Save everything, go to Unity and select File > Build Settings > iOS. Select “Run in Xcode as Debug”, after that select player settings > Other settings and set target minimum iOS version to iOS 8 (this solution won’t work on earlier versions). Click Build after that. Now select a path where you want the project to be saved. This will generate the Xcode project. Open it with Xcode.

In the Xcode file explorer expand the files on the left so you will see Frameworks > Plugins > iOS. There is a TestFramework.framework there. Drag this file into project settings > General > Embedded Binaries section:

Xcode project settings after embedding our Framework

That’s it. You can now build this project and run it on your device.

If you look at your console output in Xcode you will now be able to find this somewhere:
We are here
Hello from framework
Message from framework: Hello from Unity!

Congratulations.You can now call iOS methods from Unity. Now for the interesting part…


Calling Unity methods from native code

If you look at Unity documentation you will see that Unity already has a method for this purpose:

UnitySendMessage(“GameObjectName1”, “MethodName1”, “Message to send”);

This will work nicely if all you want to do is add a few minor native things to your existing game. But it will not work if you want to do this with a framework. The framework isn’t aware of Unity and because of that can’t actually use any of it’s code. It will take you quite a while to find a simple solution. But there is one — and it was posted on Unity forums: ( http://forum.unity3d.com/threads/making-calls-from-c-to-c-with-il2cpp-instead-of-mono_runtime_invoke.295697/ )

The solution is called function pointers. So let’s put them in action. Go back to our framework project first and open the UnityBridge.h. Modify it so it looks like this:

#import “TestClass.h”
#ifdef __cplusplus
extern “C” {
#endif

void framework_hello();
void framework_message(const char* message);
void framework_trigger_delegate();

typedef void (*DelegateCallbackFunction)(int number);
void framework_setDelegate(DelegateCallbackFunction callback);
void framework_sendMessage(int message);

#ifdef __cplusplus
}
#endif

And modify the UnityBridge.mm so it looks like:

#import “UnityBridge.h”
DelegateCallbackFunction delegate = NULL;
@interface UnityBridge : NSObject<TestDelegate>
@end
static UnityBridge *__delegate = nil;
void framework_hello() {
[TestClass displayFrameworkHello];
}
void framework_message(const char* message) {
[TestClass displayFrameworkString:[NSString stringWithUTF8String:message]];
}
void framework_trigger_delegate() {
[TestClass sendNumberToDelegate];
}
void framework_setDelegate(DelegateCallbackFunction callback) {
if (!__delegate) {
__delegate = [[UnityBridge alloc] init];
}
[TestClass setDelegate:__delegate];

delegate = callback;
}
@implementation UnityBridge
-(void)newNumberAvailable:(int)number {
if (delegate != NULL) {
delegate(number);
}
}
@end

What we do here is subscribe to the delegate of our framework and implement it’s callback. That’s pretty straightforward. We are using static objects here for the sake of simplicity. We also added two methods that we will expose to Unity. One is there just to trigger a delegate callback while the other is the important bit. framework_setDelegate will enable you to pass a function pointer to our C code from Unity. So let’s build our framework again and switch back to Unity.

First you need to delete the old Framework and replace it with the newly built one. Then open your FrameworkBridge file and modify it so it looks like this:

using UnityEngine;
using System.Collections;
using System.Runtime.InteropServices;
using AOT;
public class FrameworkBridge: MonoBehaviour {
#if UNITY_IOS
[DllImport(“__Internal”)]
private static extern void framework_hello();

[DllImport(“__Internal”)]
private static extern void framework_message(string message);

[DllImport(“__Internal”)]
private static extern void framework_trigger_delegate();

[DllImport(“__Internal”)]
private static extern void framework_setDelegate(DelegateMessage callback);

private delegate void DelegateMessage(int number);

[MonoPInvokeCallback(typeof(DelegateMessage))]
private static void delegateMessageReceived(int number) {
Debug.Log(“Message received: “ + number);
}
#endif

public static void hello() {
#if UNITY_IOS
if (Application.platform == RuntimePlatform.IPhonePlayer) {
framework_hello();
}
#endif
}

public static void message(string message) {
#if UNITY_IOS
if (Application.platform == RuntimePlatform.IPhonePlayer) {
framework_message(message);
}
#endif
}

public static void askDelegateForNumber() {
#if UNITY_IOS
if (Application.platform == RuntimePlatform.IPhonePlayer) {
framework_trigger_delegate();
}
#endif
}

public static void initializeDelegate() {
#if UNITY_IOS
if (Application.platform == RuntimePlatform.IPhonePlayer) {
framework_setDelegate(delegateMessageReceived);
}
#endif
}
}

Function pointers in C# are called delegates (more on them here: https://msdn.microsoft.com/en-us/library/ms173171.aspx ). What they enable you to do is pass functions as parameters to methods. And that is exactly what we did here. In our initializeDelegate we pass our method delegateMessageReceived to the native iOS code via the method called framework_setDelegate. Attribute called MonoPInvokeCallback is Mono specific and indicates that the methods following it will be called from the outside. Another thing worth noting here is that all of these methods must be static.

Now open your CubeHandler and modify the Start method to look like this:

void Start() {
Debug.Log(“We are here”);
FrameworkBridge.initializeDelegate();
FrameworkBridge.hello();
FrameworkBridge.message(“Hello from Unity!”);
FrameworkBridge.askDelegateForNumber();
}

All you have to do now is build the iOS app again. Save it to the same place as last time and make sure that you click Append instead of Replace. Run it on the device and you will now see a message in the output log:

Message received: 10

This means that our framework has successfully called the Unity method that we provided. This is a simple example on how you can get notified about iOS delegate events in your Unity game code. You can of course add multiple methods that pass more than one parameter and so on.

After this you would probably use something like events in C# to forward these delegate messages to any part of the code where you need them. But that really depends on what you need.


Conclusion

This was a simple introduction into a pretty advanced topic — making different programming languages and environments talk to each other. It is by no means a complete guide and there is a lot more room for improvement there. If you want to look at the technical details then I suggest you take a look at the Mono documentation ( http://www.mono-project.com/docs/advanced/pinvoke/ ).

All of this is useful if you need to either add some native functionality to your Unity game or if you want to make your iOS framework work with Unity. If you want this then you will probably also want to package everything into a nice package that your users can just drag and drop into Unity. Instructions on how to do this (and more) can be found in this pretty detailed StackOverflow answer: http://stackoverflow.com/a/14885024

I hope that you enjoyed this quick tutorial. You can follow me on Twitter or visit my personal website/blog.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.