基本
#ifdef __cplusplus
extern "C" {
#endif
// C#から呼ばれる関数
void sampleMethod1() {
NSLog(@"sampleMethod1 called.");
}
#ifdef __cplusplus
}
#endif
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)
#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
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 *
で受け取る。
配列
#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
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メソッド
#ifdef __cplusplus
extern "C" {
#endif
// C#の関数の関数ポインタ
typedef void (*cs_callback)(int);
void sampleMethod4(cs_callback callback) {
callback(9);
}
#ifdef __cplusplus
}
#endif
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を使う方がシンプルだし一般的だと思う。
C#のインスタンス
#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
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)
#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
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して返却する必要があるので注意。
配列
#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
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のインスタンス
@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
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
UnitySendMessage("GameObjectName", "MethodName", "Message to send");
UnitySendMessageは、指定したGameObjectに含まれるメソッドを呼び出す。
第3引数がパラメータになるが、文字列しか指定できない。
また、非同期となり1フレームの遅延が発生する。
C#のstaticメソッドを関数ポインタで渡す
上で記載した方法。
実装が面倒なので、問題なければ素直にUnitySendMessage使った方が良いと思う。
ビルド時の諸々対応
iOS標準のFrameworkをリンクに追加
Unityエディタ上で、ネイティブのリソース(.h, .m .mm .a .framework等)を選択し、
Inspectorから該当のフレームワークにチェックを入れておけばリンクされる。
iOS標準のライブラリをリンクに追加 (.dylib, .tbd)
PostProcessBuildで対応する。Xcode Manipulation APIを使う。
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 ());
}
}
なお、Xcode Manipulation APIが、まだ.tbdには対応できていないようなので、
対応方法は以下を参照。(たぶん近いうちに以下は不要になる)
[Unity] PostProcessBuildで*.tbdを追加する
※Unity5.3.2p1から対応されました
サードパーティのFramework/ライブラリをリンクに追加
Assets/Plugins/iOS 配下に入れておくだけでリンクされる。
info.plistに設定追加する
PostProcessBuildで対応する。
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対策で、
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
をやりたいだけであれば、iOSのPlayerSettingsを開いて
「Allow downloads over HTTP」をONにするだけで良い(デフォルトON)
ビルド時に各種フラグとか追加
PostProcessBuildで。
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」に設定すれば良い。
以上。