[Unity] C#とObjective-Cの連携まとめ

# 基本

```objc:Sample.mm
#ifdef __cplusplus
extern "C" {
#endif
    // C#から呼ばれる関数
    void sampleMethod1() {
        NSLog(@"sampleMethod1 called.");
    }
#ifdef __cplusplus
}
#endif
```

```csharp:Sample.cs
using System.Runtime.InteropServices;

public class Sample {
	// Sample.mm で定義しているCの関数を、以下のようにC#側で定義する
	[DllImport("__Internal")]
	private static extern void sampleMethod1();
}
```

Objective-C側ではCの関数を定義したソースを作る。
Objective-Cのクラスを使いたい場合は、Cの関数を通して呼び出す形になる。
Objective-Cのソースファイルは、Assets/Plugins/iOS 配下に置く。

C#側では定義したCノ関数と同名の extern static メソッドを定義する。
このstaticメソッドを呼ぶと、定義したCの関数が呼ばれる。


# 引数

## プリミティブ型 (int, float, bool)、文字列 (string)

```objc:Sample.mm
#ifdef __cplusplus
extern "C" {
#endif
    void sampleMethod2(int val1, float val2, bool val3, const char *val4) {
        NSLog(@"sampleMethod2 called. val1=%d, val2=%f, val3=%d, val4=%s", val1, val2, val3, val4);
    }
#ifdef __cplusplus
}
#endif
```

```csharp:Sample.cs
using System.Runtime.InteropServices;

public class Sample {
	[DllImport("__Internal")]
	private static extern void sampleMethod2(int val1, float val2, bool val3, string val4);
}
```

プリミティブはそのまま渡すだけ。
stringは `const char *` で受け取る。

## 配列

```objc:Sample.mm
#ifdef __cplusplus
extern "C" {
#endif
    void sampleMethod3(int *intArr, int intArrSize, const char **strArr, int strArrSize) {
        for (int i = 0; i < intArrSize; i++) {
            NSLog(@"arr1 %d : %d", i, intArr[i]);
        }
        for (int i = 0; i < strArrSize; i++) {
            NSLog(@"arr2 %d : %s", i, strArr[i]);
        }
    }
#ifdef __cplusplus
}
#endif
```

```csharp:Sample.cs
using System.Runtime.InteropServices;

public class Sample {
	[DllImport("__Internal")]
	private static extern void sampleMethod3(int[] intArr, int intArrSize, string[] strArr, int strArrSize);
}
```

ポインタで受け取る。文字列の場合は、`const char` のダブルポインタになる。
配列のサイズも一緒に渡すと良いと思う。


## staticメソッド

```objc:Sample.mm
#ifdef __cplusplus
extern "C" {
#endif
    // C#の関数の関数ポインタ
    typedef void (*cs_callback)(int);
    
    void sampleMethod4(cs_callback callback) {
        callback(9);
    }
#ifdef __cplusplus
}
#endif
```

```csharp:Sample.cs
using System.Runtime.InteropServices;
using AOT;

public class Sample {
	delegate void callback_delegate(int val);

	[DllImport("__Internal")]
	private static extern void sampleMethod4(callback_delegate callback);

	// コールバック関数を、MonoPInvokeCallbackを付けてstaticで定義
	[MonoPInvokeCallback(typeof(callback_delegate))]
	private static void cs_callback(int val) {
		UnityEngine.Debug.Log ("cs_callback : " + val);
	}

	private static void sampleMethod4Invoker() {
        // staticメソッドを直接引数に渡せる
		sampleMethod4 (cs_callback);
	}
}
```

C#のstaticメソッドを、関数ポインタとしてObjective-Cに渡すことができる。
コールバック関数を渡して、処理完了後に呼び出してもらうことが可能。

ただ、Objective-CからC#のコールバックを呼び出す場合、
[UnitySendMessage](http://docs.unity3d.com/ja/current/Manual/PluginsForIOS.html)を使う方がシンプルだし一般的だと思う。


## C#のインスタンス

```objc:Sample.mm
#ifdef __cplusplus
extern "C" {
#endif
    // C#のSampleクラスのインスタンス
    typedef void *_Mono_Sample_Instance;

    // C#の関数の関数ポインタ
    typedef void (*cs_callback)(_Mono_Sample_Instance, int);
    
    void sampleMethod5(_Mono_Sample_Instance instance) {
        NSLog(@"sampleMethod5 called.");
        // C#のコールバック呼び出し
        cs_callback(instance, 999);
    }
#ifdef __cplusplus
}
#endif
```

```csharp:Sample.cs
using System.Runtime.InteropServices;

public class Sample {
	delegate void callback_delegate(IntPtr gameObjectPtr, int val);

	[DllImport("__Internal")]
	private static extern void sampleMethod5(IntPtr gameObjectPtr, callback_delegate callback);

	[MonoPInvokeCallback(typeof(callback_delegate))]
	private static void cs_callback(IntPtr gameObjectPtr, int val) {
		GCHandle handle = (GCHandle) gameObjectPtr;
		// ポインタからオブジェクトを取得する
		GameObject gameObject = handle.Target as GameObject;
		// 不要になったタイミングで、AllocしたオブジェクトはFreeする
		handle.Free ();

		UnityEngine.Debug.Log ("cs_callback : " + gameObject.name);
	}

	private static void sampleMethod5Invoker() {
		GameObject gameObject = new GameObject ("Sample_GameObject");
		// GCに解放されないように、Allocする
		IntPtr gameObjectPtr = (IntPtr)GCHandle.Alloc (gameObject);
		sampleMethod5 (gameObjectPtr, cs_callback);
	}
}
```

C#のインスタンスもIntPtrで渡すことができる。
使い道としては、Objective-Cに渡したC#のインスタンスを、コールバックでまたC#に
渡してもらう形になると思う。
これを使うと、上記で記載したstaticメソッドを渡してコールバックを呼ぶ時に、
そこからインスタンスメソッドを呼ぶ事ができる。


# 戻り値

## プリミティブ型 (int, float, bool)、文字列 (string)


```objc:Sample.mm
#ifdef __cplusplus
extern "C" {
#endif
    int sampleMethod6() {
        return 5;
    }

    float sampleMethod7() {
        return 1.1f;
    }

    bool sampleMethod8() {
        return true;
    }

    const char* sampleMethod9() {
        const char *str = "test";
        char* retStr = (char*)malloc(strlen(str) + 1);
        strcpy(retStr, str);
        retStr[strlen(str)] = '\0';
        return retStr;
    }
#ifdef __cplusplus
}
#endif
```

```csharp:Sample.cs
using System.Runtime.InteropServices;

public class Sample {
	[DllImport("__Internal")]
	private static extern int sampleMethod6();

	[DllImport("__Internal")]
	private static extern float sampleMethod7();

	[DllImport("__Internal")]
	private static extern bool sampleMethod8();

	[DllImport("__Internal")]
	private static extern string sampleMethod9();
}
```

文字列はC側でmallocして返却する必要があるので注意。

## 配列

```objc:Sample.mm
#ifdef __cplusplus
extern "C" {
#endif
    void sampleMethod10(int **arrPtr, int *size) {
        int arr[] = {1, 2, 3};
        int *retArr = (int *)malloc(sizeof(arr));
        memcpy(retArr, arr, sizeof(arr));
        *arrPtr = retArr;
        *size = sizeof(arr) / sizeof(arr[0]);
    }
#ifdef __cplusplus
}
#endif
```

```csharp:Sample.cs
using System;
using System.Runtime.InteropServices;

public class Sample {
	[DllImport("__Internal")]
	private static extern void sampleMethod10(out IntPtr arrPtr, out int size);

	private static int[] sampleMethod10Invoker() {
		IntPtr arrPtr = IntPtr.Zero;
		int size = 0;
		sampleMethod10 (out arrPtr, out size);
		int[] arr = new int[size];
		Marshal.Copy (arrPtr, arr, 0, size);
		return arr;
	}
}
```

だいぶ面倒な感じ。
単純に`return`で返却は無理っぽい（サイズも返却する必要がある）ので、
C#から変数を参照渡し（out / ref）して受け取る。配列は`IntPtr`で受ける。
C側では`malloc`で確保した配列のポインタとそのサイズを、参照渡しされた変数にセットする。
最後に`IntPtr`で受け取った配列を、`Marshal.Copy`でC#の配列にコピーする。

## Objective-Cのインスタンス


```objc:Sample.mm

@interface Sample : NSObject
- (void)test;
@end

@implementation Sample
- (void)test {
    NSLog(@"test called.");
}
@end

#ifdef __cplusplus
extern "C" {
#endif
    Sample* sample_init() {
        Sample *sample = [[Sample alloc] init];
        CFRetain((CFTypeRef)sample);
        return sample;
    }

    void sample_test(Sample *sample) {
        [sample test];
    }

    void cfRelease(id *obj) {
        CFRelease((CFTypeRef)obj);
    }
#ifdef __cplusplus
}
#endif
```

```csharp:Sample.cs
using System;
using System.Runtime.InteropServices;

public class Sample {
	[DllImport("__Internal")]
	private static extern IntPtr sample_init();

	[DllImport("__Internal")]
	private static extern void sample_test(IntPtr samplePtr);

	[DllImport("__Internal")]
	private static extern void cfRelease(IntPtr samplePtr);

	private static void sampleInvoker() {
		IntPtr objcPtr = sample_init ();
		sample_test (objcPtr);
		cfRelease (objcPtr);
	}
}
```

Objective-Cのインスタンスは、C#側でIntPtrで保持する事ができる。
Objective-Cのインスタンスの生存期間をC#側で管理したい場合に便利。
Objective-C側は`retain`してからオブジェクトをC#に返却しないと解放されてしまうので注意。
C#のコンストラクタでObjective-Cのインスタンスを生成、かつretainして、
デストラクタでreleaseすると良いかも。


# Objective-CからC#のメソッドを呼び出す

## UnitySendMessage

```objc
UnitySendMessage("GameObjectName", "MethodName", "Message to send");
```
http://docs.unity3d.com/ja/current/Manual/PluginsForIOS.html

UnitySendMessageは、指定したGameObjectに含まれるメソッドを呼び出す。
第３引数がパラメータになるが、文字列しか指定できない。
また、非同期となり１フレームの遅延が発生する。

## C#のstaticメソッドを関数ポインタで渡す

上で記載した方法。
実装が面倒なので、問題なければ素直にUnitySendMessage使った方が良いと思う。


# ビルド時の諸々対応

## iOS標準のFrameworkをリンクに追加

Unityエディタ上で、ネイティブのリソース(.h, .m .mm .a .framework等)を選択し、
Inspectorから該当のフレームワークにチェックを入れておけばリンクされる。

## iOS標準のライブラリをリンクに追加 (.dylib, .tbd)
PostProcessBuildで対応する。Xcode Manipulation APIを使う。

```csharp:Assets/Editor/PostBuildProcess.cs
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.IO;

public class PostBuildProcess {
	
	[PostProcessBuild]
	public static void OnPostProcessBuild (BuildTarget buildTarget, string path) {
		string projPath = Path.Combine (path, "Unity-iPhone.xcodeproj/project.pbxproj");
		
		PBXProject proj = new PBXProject ();
		proj.ReadFromString (File.ReadAllText (projPath));
		
		string target = proj.TargetGuidByName ("Unity-iPhone");
		proj.AddFileToBuild(target, proj.AddFile("usr/lib/libsqlite3.tbd", "Frameworks/libsqlite3.tbd", PBXSourceTree.Sdk));
		proj.AddFileToBuild(target, proj.AddFile("usr/lib/libz.tbd", "Frameworks/libz.tbd", PBXSourceTree.Sdk));
		
		File.WriteAllText (projPath, proj.WriteToString ());
	}
}
```



<del>なお、[Xcode Manipulation API](https://bitbucket.org/Unity-Technologies/xcodeapi)が、まだ.tbdには対応できていないようなので、
対応方法は以下を参照。（たぶん近いうちに以下は不要になる）
[[Unity] PostProcessBuildで*.tbdを追加する](http://qiita.com/tkyaji/items/19dfff4afe228c7f34a1)</del>
**※Unity5.3.2p1から対応されました**

## サードパーティのFramework／ライブラリをリンクに追加

Assets/Plugins/iOS 配下に入れておくだけでリンクされる。


## info.plistに設定追加する

PostProcessBuildで対応する。

```csharp:Assets/Editor/PostBuildProcess.cs
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.IO;

public class PostBuildProcess {
	
	[PostProcessBuild]
	public static void OnPostProcessBuild (BuildTarget buildTarget, string path) {
		string plistPath = Path.Combine(path, "Info.plist");
		PlistDocument plist = new PlistDocument();
		plist.ReadFromFile(plistPath);
		
		PlistElementDict rootDict = plist.root;
		
		PlistElementArray urlTypesArray = rootDict.CreateArray ("CFBundleURLTypes");
		PlistElementDict dict = urlTypesArray.AddDict ();
		dict.SetString ("CFBundleURLName", "xxxxx");
		PlistElementArray schemesArray = dict.CreateArray ("CFBundleURLSchemes");
		schemesArray.AddString ("xxxxx");
		
		File.WriteAllText(plistPath, plist.WriteToString());
	}
}
```

なお、ATS対策で、

```xml
<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>
```

をやりたいだけであれば、iOSのPlayerSettingsを開いて
「Allow downloads over HTTP」をONにするだけで良い（デフォルトON）

## ビルド時に各種フラグとか追加

PostProcessBuildで。

```csharp:Assets/Editor/PostBuildProcess.cs
using UnityEngine;
using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using System.IO;

public class PostBuildProcess {
	
	[PostProcessBuild]
	public static void OnPostProcessBuild (BuildTarget buildTarget, string path) {
		string projPath = Path.Combine (path, "Unity-iPhone.xcodeproj/project.pbxproj");
		
		PBXProject proj = new PBXProject ();
		proj.ReadFromString (File.ReadAllText (projPath));
		
		string target = proj.TargetGuidByName ("Unity-iPhone");
		// Enabled Modules = YES
		proj.SetBuildProperty (target, "CLANG_ENABLE_MODULES", "YES");
		// Enable Objective-C Exceptions = YES
		proj.SetBuildProperty (target, "GCC_ENABLE_OBJC_EXCEPTIONS", "YES");
		// "Other Linker Flags" に "-ObjC" を追加
		proj.AddBuildProperty (target, "OTHER_LDFLAGS", "-ObjC");

		File.WriteAllText (projPath, proj.WriteToString ());
	}
}
```

設定するキー名がわからない場合は、.xcodeproj の中の project.pbxproj を
テキストで開いて中を見るのが手取り早いと思う。

pbxprojとinfo.plistの編集については、基本 PostProcessBuild でできるはず。
PBXProjectクラスでうまく設定できなくても、最悪読み込んだ文字列を置換すればいける。

なお、ソースに対して "-fno-objc-arc" フラグを付けたい場合は、Unityエディタ上で
対象のソースを選択し、Inspectorで 「Compile flags」に設定すれば良い。


以上。