はじめに
UnityからiOSの機能を呼び出して、さらにiOSからUnityへ戻り値を渡したかった。
しかし、ぐぐってもなかなか良い方法が出てこない。
(UnitySendMessageは文字列しか返せないのがイマイチなので使う気がない。)
実際にはすべてのコールバック関数が呼べないわけではない。
staticな関数なら普通にiOSから呼び出せる。
だが、呼び出したいのはインスタンスメソッドなのだ。
しょうがないので、自力ででっちあげることにした。
方針
staticなメソッドなら呼べるので、staticなメソッドを踏み台にしてインスタンスメソッドを呼び出せば良いのでは?
しかし、ただ単純に MethodName(arg1, arg2, staticなメソッド, インスタンスメソッド)
のように呼び出してもエラーが出てダメ。
どうもインスタンスメソッドがGCされてしまうのが問題らしい(たぶん)。
そこでGCされてしまわないように、GCHandle.Alloc()を使うといけそうだ。
コード
iOS (Objective-C)
今回、ネイティブ側は渡された数を2倍するだけの単純なものにした。
以下のCallback.hとCallback.mmをAssets/Plugins/iOSフォルダに入れておく。
extern "C" {
typedef void (*CallbackCaller)(int x, void* methodHandle);
void _CallPlugin (int x, void* methodHandle, CallbackCaller caller);
}
# include "Callback.h"
void _CallPlugin (int x, void* methodHandle, CallbackCaller caller){
(caller)(x * 2, methodHandle); // 2倍したらコールバック(を呼び出すメソッド)を呼ぶ。
}
Unity (C#)
using System;
using System.Runtime.InteropServices;
public class SampleCallback {
// 「コールバックしたいメソッドの型」を宣言
public delegate void Callback (int x);
// 「コールバックしたいメソッドの引数を並べて宣言」 + メソッドのハンドル
public delegate void CallbackCaller(int x, IntPtr methodHandle);
// 「コールバックしたいメソッドの引数を並べて宣言」 + メソッドのハンドル + コールバックを呼び出すためのstaticなメソッド
[DllImport("__Internal")]
private static extern void _CallPlugin (int x, IntPtr methodHandle, CallbackCaller caller);
// C#(Unity)から呼び出すメソッド
public static void CallPlugin (int x, Callback callback) {
// コールバック関数をGCされないようにAllocしてハンドルを取得する。
IntPtr gcHandle = (IntPtr)GCHandle.Alloc(callback, GCHandleType.Normal);
// 普通の引数 + コールバック関数のハンドル + コールバック関数を呼び出すためのstaticなメソッド
_CallPlugin (x, gcHandle, CallCallback);
}
// コールバックを呼び出すためのstaticなメソッド(iOSから呼ばれる)
[AOT.MonoPInvokeCallbackAttribute(typeof(CallbackCaller))]
static void CallCallback (int x, IntPtr methodHandle){
// methodHandleからコールバック関数を取り出す。
GCHandle handle = (GCHandle)methodHandle;
Callback callback = handle.Target as Callback;
// 不要になったハンドルを解放する。
handle.Free();
callback(x);
}
}
上記のクラスを呼ぶ出すのは以下のような感じ。
public void Multiply(){
SampleCallback.CallPlugin(7, OnEndOfCalculate); // 7を2倍したい。
}
void OnEndOfCalculate(int x){
Debug.LogFormat("x: {0}", x); // 14が返ってくるはず。
}
サンプル
GitHubにサンプルを上げときましたので参考にしてください。
参考
NativePluginsにC#デリゲートを登録する
Xamarin iOSのP/Invokeでコールバックを使うときの制限
[Unity] C#とObjective-Cの連携まとめ