68

I have a very tiny Objective-C library built for iOS and I want to export it to Unity. I understand the basic process of writing a csharp wrapper that marshals all the invocations to native library, but I completely have no idea where to start. Could anyone please explain step-by-step how to create a unity package with my library so I could also distribute it to other developers.

Unity3d documentation is pretty brief and does not explain anything.

Thanks.

pronebird
  • 12,068
  • 5
  • 54
  • 82
  • 4
    I had to do this recently at work. The Unity documentation is not very helpful and I couldn't find any useful info on the web. It'll take me a while to put a step-by-step together so if nobody else puts one up first I'll answer this question later today. – ChrisH Feb 12 '13 at 17:21
  • Maybe [this answer](http://answers.unity3d.com/questions/147755/native-alert-on-ios-plugin.html) from the Unity site can help you. See my comments to get a link via wayback machine to the blog posting. – Kay Feb 12 '13 at 17:44
  • http://stackoverflow.com/questions/8321939/how-to-use-an-xcode-game-on-unity3d/8333120#8333120 and http://stackoverflow.com/questions/4272413/mixing-unity-generated-code-with-objective-c-in-ios/7076887#7076887 might help as well – Kay Feb 12 '13 at 17:47
  • @Kay, thanks, I already checked all those links, not much insight in how to build unity package. Github search for "unity ios plugin" was much more useful than google. I am going to continue tomorrow, now I slightly understand the structure of plugin project. – pronebird Feb 12 '13 at 18:27
  • @ChrisH I would really appreciate if you could help me with this. – pronebird Feb 12 '13 at 18:27
  • @Andy I haven't forgotten about this...my Unity license expired and I'm waiting on a new one. Can barely remember how I did this so need to retrace my steps a bit. – ChrisH Feb 13 '13 at 17:43
  • @ChrisH it seems that I already figured it out, thanks anyway :-) – pronebird Feb 13 '13 at 20:01

1 Answers1

90

Okay, after playing few days with Unity3d on Mac I finally figured it out. All the code in this guide is dummy. I have written this stuff in 15 minutes or so, so don't be bothered by mistakes and typos.

1) Open Unity, create new project (File -> New Project) and save it somewhere

2) When the project is generated it has the following structure:

  • ProjectName/Assets (That's what you need)
  • ProjectName/Library (Nevermind what's there)
  • ProjectName/ProjectSettings (You don't care about it)
  • ProjectName/ProjectName.sln (MonoDevelop project)

3) Go to ProjectName/Assets and create the following folders: Plugins/iOS, so in the end you'll have a folder structure like this: ProjectName/Assets/Plugins/iOS

4) Put your compiled library (.a) file and necessary headers inside of ProjectName/Assets/Plugins/iOS or copy the source code of your library there (.mm, .h, .m, etc..). Remember, normally you can only access C-functions from C#, so you'll have to wrap your Objective-C stuff in C-code somehow, in my case all Objective-C objects were implemented in a form of Singleton so it wasn't hard to make a C-style wrapper around, for instance:

CWrapper.h:

extern "C" void MySDKFooBarCFunction();

CWrapper.mm

#import "CWrapper.h"
#import "MyObjectiveCLibrary.h" // your actual iOS library header

void MySDKFooBarCFunction() {
    [MyObjectiveCLibrary doSomeStuff];
}

5) Then go to ProjectName/Assets and create a folder for CSharp wrapper class(es), call it whatever you want, for example: ProjectName/Assets/MySDK

6) Inside of MySDK folder create MySDK.cs file, the dummy example of C# wrapper would look like this:

using UnityEngine;
using System;
using System.Runtime.InteropServices;

public class MySDK
{
    // import a single C-function from our plugin
    [DllImport ("__Internal")]
    private static extern void MySDKFooBarCFunction();

    // wrap imported C-function to C# method
    public static void FooBarCFunction() {
        // it won't work in Editor, so don't run it there
        if(Application.platform != RuntimePlatform.OSXEditor) {
            MySDKFooBarCFunction();
        }
    }
}

7) Create a shell script to pack this stuff into .unitypackage and put it next to your project folder (not inside). Adjust EXPORT_PATH and PROJECT_PATH variables in the script for your needs.

#!/bin/sh

WORKDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
UNITY_BIN="/Applications/Unity/Unity.app/Contents/MacOS/Unity"
EXPORT_PATH="${WORKDIR}/ProjectName.unitypackage"
PROJECT_PATH="${WORKDIR}/ProjectName"
ASSETS_PATH="Assets"

$UNITY_BIN -batchmode -quit \
-logFile export.log \
-projectPath $PROJECT_PATH \
-exportPackage $ASSETS_PATH $EXPORT_PATH

8) Run the created bash script to get your package build. All stuff from Assets will be included in XCode project for your Unity Project when you generate it via File -> Build Settings in Unity Editor. You can use generated package to distribute your code to other developers so they can simply include your library to their Unity projects by double clicking on the package file.

Don't forget to shutdown Unity Editor when you run this script, otherwise it may fail to build a package.

If you have some issues and package does not show up, this script always prints log to export.log

Next steps make sense only if you want to make a Demo unity project for your library (good for testing at least)

9) You can put created Unity project (ProjectName.unity) to Assets/MySDKDemo so you have a demo inside of your package.

10) Create a simple script for your Demo Unity3d scene at Assets/MySDKDemo/MySDKDemo.cs, for example:

using UnityEngine;
using System;
using System.Collections;

public class MySDKDemo : MonoBehaviour
{   
    private GUIStyle labelStyle = new GUIStyle();
    private float centerX = Screen.width / 2;

    // Use this for initialization
    void Start ()
    {   
        labelStyle.fontSize = 24;
        labelStyle.normal.textColor = Color.black;
        labelStyle.alignment = TextAnchor.MiddleCenter;
    }

    void OnGUI ()
    {
        GUI.Label(new Rect(centerX - 200, 20, 400, 35), "MySDK Demo", labelStyle);
        if (GUI.Button(new Rect(centerX - 75, 80, 150, 35), "DoStuff"))
        {
            MySDK.FooBarCFunction();
        }
    }

}

11) Go to Unity Editor. Find the "Main Camera" in left sidebar in Unity Editor, select it and in the bottom of Inspector panel (right sidebar) click on AddComponent, select Scripts -> MySDKDemo script

12) Build the XCode project and run on device.

Few notes

1) Plugins don't work in Unity Editor, simply because they're not compiled in the real-time, well, not sure but probably until you use C# in your plugins, probably C# stuff gets linked immidiately and works in Editor environment.

2) This post does not cover marshaling, or data/memory management between native <-> managed code, as it is very well documented.

Interop with Native Libraries @ Mono project

3) Callbacks from C# to C can be passed using C# delegates, on C-side you use standard functions declarations, on C# side you declare delegates with the same signature. It seems that booleans, integers and strings (C: char*) are marshalled flawlessly (I don't talk about memory management policy and who's responsible to release memory or return value policies).

However it will not work on iOS builds out-of-box due to platform limitations, but C#-to-C callbacks still can be implemented using MonoPInvokeCallbackAttribute, useful links on this topic:

Actually in Unity 4 there's AOT.MonoPInvokeCallbackAttribute already implemented, it's limited to static delegates that can be passed to unmanaged code, but still better than nothing.

4) There's a way to get Unity RootViewController using UnityGetGLViewController function. Just declare this function in your implementation file, i.e.:

extern UIViewController *UnityGetGLViewController();

And use UnityGetGLViewController() whenever you need to get an access to RootViewController.

5) There's much more magic and ugly stuff in details, keep your C interfaces as simple as possible otherwise marshalling can become your nightmare and also keep in mind that managed-to-unmanaged is generally expensive.

6) You definitely use some frameworks in your native code and you don't want linker problems. For example, if you use Keychain in your library then you need to include Security.framework into Xcode project.

I suggest to give a try to XUPorter, it helps Unity to integrate any additional dependencies into Xcode project.

Good luck!

pronebird
  • 12,068
  • 5
  • 54
  • 82
  • Thanks, your example only calls into a function. Can you get data back from Objective-C using this same approach above, by passing variables by reference or have the function return object? – Boon Jul 08 '14 at 15:25
  • @Boon yes you can, but you will have to follow certain rules to make sure that data sizes match and memory ownership is properly transferred. This process called marshaling and there are lots of articles on this topic, you might find it useful to read "Interop with Native libraries" at Mono project website. – pronebird Jul 08 '14 at 18:23
  • Andy, I read something about UnitySendMessage, how does that work in the context of your answer? Thanks again for this write-up, this is the only step-by-step I found that's very clear. – Boon Jul 08 '14 at 21:33
  • @Andy, I am not following step 9 as I am not seeing any file called ProjectName.unity. Do you mean unitypackage? – Boon Jul 08 '14 at 21:43
  • @Boon that's a Unity3d project with scene created in Unity Editor. This step is optional. – pronebird Jul 08 '14 at 23:20
  • Hi Andy, I want to try your optional step, but I am not finding any file with .unity extension to add to the folder. – Boon Jul 09 '14 at 01:24
  • @Boon `UnitySendMessage` can be used when you need to send a message from native to game object. But as you can see it's very limited as it needs receiver name and method for delivery and it only supports string message. – pronebird Jul 09 '14 at 09:49
  • @Boon Glad you find this post useful. :) I used Unity 4.0 at the time when I posted this. On the first step you create a project in Unity Editor and you save it on disk. Check what files Unity produces when you create new project. One of those is a project or scene file, it used to be with .unity extension IIRC. Things might be different since 4.0 though... – pronebird Jul 09 '14 at 09:53
  • Thanks Andy - the scene file has .unity extension, just that the name doesn't match the project name as Unity allows you to save it with whatever name you want. – Boon Jul 09 '14 at 13:08
  • @Boon ProjectName is a placeholder that I use in this guide, it really means whatever name you used while saving scene. – pronebird Jul 09 '14 at 13:24
  • Got it. Andy, your example mainly shows how to call native method from Unity. How would I send data from native to Unity using your example? Is there a list somewhere that tells us how the various types of native data is marshaled across (e.g. char * to string)? – Boon Jul 09 '14 at 13:31
  • @Boon Information on data transfer between native and managed is well documented and beyond this post of course. Check out "4. Marshaling" section at http://www.mono-project.com/Interop_with_Native_Libraries – pronebird Jul 10 '14 at 09:35
  • @Andy , Thanks for such a great help, a detailed one! The only thing confusing is from step 9, but ignorable as it's optional. – Mohammad Zaid Pathan Dec 01 '15 at 11:18
  • @Andy Can we do *Step 6* for Javascript instead of C#? – Mohammad Zaid Pathan Dec 08 '15 at 13:43
  • I don't understand your bash script. You call $UNITY_BIN as if it were a command but it is a directory. What command does the bash script run? – dumbledad Feb 10 '17 at 10:19
  • 1
    @dumbledad why do you think it is a directory? It's an executable. If you're on Windows, you have to replace it with corresponding path on Windows Installation, probably should have `Unity.exe` in it. – pronebird Feb 10 '17 at 10:21