iOS 與 Unity 消息傳遞 (Swift 與 C#)

image

背景知識

Unity 在導(dǎo)出 iOS 工程的時候支持兩種模式:mono 和 IL2CPP。

mono 就是傳統(tǒng)的虛擬機(jī)模式。這種模式只支持 32 位的 cpu帝璧,根據(jù)官方的答復(fù),以后也不會有更多的支持了。現(xiàn)在 iOS 上架 AppStore 必須要支持 arm64称龙,所以 mono 模式就是看看就好了。

IL2CPP IL(中間語言)轉(zhuǎn) C++戳晌,也就是把 C# 編譯成的 IL鲫尊,再從 IL 轉(zhuǎn)成 C++,然后跟 iOS 代碼一起混合編譯沦偎。所以基于 IL2CPP 的Unity 與 iOS 原生接口的相互調(diào)用疫向,本質(zhì)上就是 C++ 和 Objective C 的相互調(diào)用,理解了這個豪嚎,我們做互相調(diào)用的開發(fā)就很容易了搔驼。

Swift 參數(shù)與 C# 轉(zhuǎn)換

函數(shù)參數(shù)

例如 void UnitySendMessage(const char* obj, const char* method, const char* msg);

String 轉(zhuǎn) const char* => url.cString(using: String.defaultCStringEncoding)

函數(shù)返回值

我們不能在函數(shù)內(nèi)存申請局部變量去返回給 C# 用,因為函數(shù)退出的時候侈询,內(nèi)存就釋放了舌涨,那么我們需要用 malloc 去分配內(nèi)存。malloc 分配的內(nèi)存不需要調(diào)用 free扔字,也不用擔(dān)心會內(nèi)存泄露囊嘉,因為合成的代碼會自動幫忙處理的。

int 數(shù)組

static int *intArr = NULL;
int* getAllBotId() {
    NSArray *array = [SwiftCommon getAllBotId];
    intArr = malloc(array.count);
    for (int i = 0; i < array.count; i++) {
        intArr[i] = [[array objectAtIndex:i] intValue];
    }
    return intArr;
}

char 數(shù)組

char* getRobotCardsInfo() {
    NSString *string = [SwiftCommon getRobotCardsInfo];
    char* cStringCopy(const char* string);
    return cStringCopy([string UTF8String]);
}

char* cStringCopy(const char* string){
    if (string == NULL){
        return NULL;
    }
    char* res = (char*)malloc(strlen(string)+1);
    strcpy(res, string);
    return res;
}

通信一:Unity 向 iOS 發(fā)消息

Unity 聲明一個 [DllImport( "__Internal" )] 函數(shù)

iOS .m 文件寫函數(shù)實現(xiàn)

通信二:iOS 向 Unity 發(fā)消息

方法一:UnitySendMessage

Unity 自身提供了一個函數(shù)革为,可以將信息通過字符串或 json 字符串傳入

/**
  * iOS 主動發(fā)消息給 Unity
  * @param obj     Unity GameObject名稱
  * @param method  GameObject綁定腳本的方法
  * @param msg     要傳遞的消息
  */
void UnitySendMessage(const char* obj, const char* method, const char* msg);

方法二: 指針

原理:C++ 里面有函數(shù)指針的概念扭粱,我們可以在 iOS 端實現(xiàn)一個函數(shù),接收函數(shù)指針篷角,Unity 側(cè)調(diào)用焊刹,把 Unity 的函數(shù)當(dāng)做指針傳過去,在 iOS 端把它存下來,后續(xù)使用虐块。

示例:將一個 string 傳到 unity, 代碼量比 UnitySendMessage 多俩滥,但這是為下一個例子做鋪墊,通過指針回調(diào)以一種更優(yōu)雅的方式傳回數(shù)據(jù)

TtsPlayeriOSProxy.cs

[MonoPInvokeCallback]
public static int playTtsUrl(string url) {
    // 收到 iOS 傳來的 url
}

[DllImport("__Internal")]
private static extern void setTtsFunction(FunctionPlayer urlPlayer);

// ios call c# delegate
public delegate int FunctionPlayer(string url);

...

// 單例構(gòu)造函數(shù)
private TtsPlayeriOSProxy ()
{
    setTtsFunction (playTtsUrl);
}
        
UnityPlugin.m 

// C#設(shè)置過來的函數(shù)指針類型
typedef int (*PlayTtsUrlFunction)(const char *url);

static PlayTtsUrlFunction playTtsUrlFunc;

void setTtsFucntion(PlayTtsUrlFunction urlFunction) {
    playTtsUrlFunc = urlFunction;
}

void playTtsUrl(const char* url) {
    if (playTtsUrlFunc != nil) {
        playTtsUrlFunc(url);
    }
}

在 .swift 文件中可直接調(diào)用 playTtsUrl 函數(shù)將 url 傳遞到 Unity

知識補(bǔ)充:

1. C# delegate

C# 中的委托(Delegate)類似于 C 或 C++ 中函數(shù)的指針贺奠。委托(Delegate) 是存有對某個方法的引用的一種引用類型變量霜旧。
例如,假設(shè)有一個委托:

public delegate int MyDelegate (string s);

上面的委托可被用于引用任何一個帶有一個單一的 string 參數(shù)的方法儡率,并返回一個 int 類型變量挂据。

2. [DllImport( "__Internal" )]
private static extern void sendRequest(int requestId, byte[] data, int dataLength);

[DllImport( "__Internal" )] 表示這個函數(shù)位于Dll中,DLL名字是 __Internal儿普,這是固定語法崎逃,意思是這個函數(shù)是靜態(tài)鏈接在 iOS 的 App 中的。extern 表示是一個外部的函數(shù)眉孩。

3. [MonoPInvokeCallback]

用來標(biāo)記這個函數(shù)會被iOS側(cè)反向調(diào)用

通信三:Unity 將任務(wù)交付 iOS 處理个绍,結(jié)果回調(diào) Unity

示例:Unity 組裝數(shù)據(jù) encode 成 byte 數(shù)組交付 iOS 發(fā)起網(wǎng)絡(luò)請求

image

Unity

NetworkManageriOSProxy.cs 單例類

[DllImport( "__Internal" )]
private static extern void sendRequest(int requestId, byte[] data, int dataLength);
[DllImport("__Internal")]
private static extern int setFunctionPointerOnResponse(FunctionPointerOnResponse pointer);

// ios call c# delegate
public delegate void FunctionPointerOnResponse(int reqeustId, int errorCode, IntPtr responseData, int responseDataLength);
    
[MonoPInvokeCallback]
// 需要 length 重組數(shù)據(jù)
static public void onResponse(int reqeustId, int errorCode, IntPtr responseData, int responseDataLength)
{            
    if (errorCode == 0)
    {
        byte[] buffer = new byte[responseDataLength];         
        Marshal.Copy(responseData, buffer, 0, responseDataLength);
       NetworkManagerProxy.onResponse(reqeustId, errorCode, buffer);    
    }
}

...
    
private static bool isCallbackSeted = false;
    
public static void sendRequestForiOS(int requestId, byte[] data)
{
    if (!isCallbackSeted) {
        setFunctionPointerOnResponse(onRespone);
        isCallbackSeted = true;
    }
    sendRequest(requestId, data, data.Length);
}


針對 onResponse 函數(shù)的 responseData 參數(shù)做一個解釋

iOS c++: void onRespone(int reqeustId, int errorCode, const void responseData*, int responseDataLength);
Unity c#: static public void onResponse(int reqeustId, int errorCode, IntPtr responseData, int responseDataLength);

iOS側(cè) 調(diào)用 C# 的時候,基本類型是可以直接映射的浪汪,要注意的是巴柿,如果是數(shù)組之類的參數(shù),在 iOS 側(cè)用 C++ 的表現(xiàn)是指針死遭,這個其實是內(nèi)存里面的 rawdata广恢,會以一個 IntPtr 的參數(shù)傳遞到 Unity 側(cè),我們知道在 C# 中數(shù)組是個對象呀潭,我們要把它轉(zhuǎn)為“托管的對象”钉迷。上面的代碼有示例如何轉(zhuǎn)換。

iOS

.m文件

// C#設(shè)置過來的函數(shù)指針類型
typedef void (*FunctionPointerOnResponse)(int reqeustId, int errorCode, void* responseData, int responseDataLength);
// 用于保存回調(diào)指針的
static FunctionPointerOnResponse callbackFunction;

void setFunctionPointerOnResponse(FunctionPointerOnResponse pointer) {
    callbackFunction = pointer;
}

// 需要 length 重組數(shù)據(jù)
void sendRequest(int requestId, Byte* data, int dataLength) {
    NSData *body = [[NSData alloc] initWithBytes:data length:dataLength];
    [SwiftCommon sendUnityRequest:requestId body:body];
}


// 收到數(shù)據(jù)的時候回調(diào)此接口
void onRespone(int reqeustId, int errorCode, const void* responseData, int responseDataLength) {
    if (callbackFunction) {
        callbackFunction(reqeustId, errorCode, (void *)(responseData), responseDataLength);
    }
}

SwiftCommon.swift

/**
    OC調(diào)用swift中不支持的類型的方法
 */
class SwiftCommon: NSObject {
    // 偽代碼
    @objc static func sendUnityRequest(_ requestId: Int, body: NSData) {
        let request = xxx
        NetworkEngine.shared.sendRequest(request, success: { response in
            guard var data = response.data else {
                return
            }
            let dataLength = Int32(data.count)
            data.withUnsafeMutableBytes({ (bytes: UnsafeMutablePointer<UInt8>) -> Void in
                // 回傳給 unity
                onRespone(Int32(requestId), response.ret, bytes, dataLength)
            })
        }) { error in
        
        }
    }
}

通信四:iOS 將任務(wù)交給 Unity 處理蜗侈,結(jié)果回調(diào) iOS

image

Unity

通信三中寫了如何將指針傳入iOS篷牌, 這里不再贅述傳遞 start 和 stop 指針的 c# 代碼 

iOSASRListener.cs

[DllImport("__Internal")]
private static extern void _HandleASRSuccessed(string requestId, bool isFinish, string msgText, string rspMsg, byte[] msgResponse, int msgResponeLength);
[DllImport("__Internal")]
private static extern void _HandleASRFailed(string requestId, int errorCode);

iOS

AsrProxy.m

#ifdef __cplusplus
extern "C" {
#endif

// C#設(shè)置過來的函數(shù)指針類型
typedef void (*FunctionPointerForAsr)(void);
    
static FunctionPointerForAsr start;
static FunctionPointerForAsr stop;

void setFunctionPointerForAsr(FunctionPointerForAsr startFunc, FunctionPointerForAsr stopFunc)
{
    start = startFunc;
    stop = stopFunc;
}

void StartAsr() 
{
    if (start)
    {
        start();
    }
}

void StopAsr() 
{
    if (stop)
    {
        stop();
    }
}

#ifdef __cplusplus
}
#endif
AsrListenerProxy.m

extern void (^ __nonnull HandleASRSuccessed_SwiftCallBack)(const char* __nonnull requestId, bool isFinish, const char* __nonnull msgText, const char* __nonnull rspMsg, NSData* msgResponseData) = NULL;
extern void (^ __nonnull HandleASRFailed_SwiftCallBack)(const char* __nonnull requestId, int errorCode) = NULL;

void _HandleASRSuccessed(const char* requestId, bool isFinish, const char* msgText, const char* rspMsg, Byte* msgResponse, int msgResponeLength)
{
    if (HandleASRSuccessed_SwiftCallBack != NULL ){
        NSData * data = [[NSData alloc]initWithBytes:msgResponse length:msgResponeLength];
        HandleASRSuccessed_SwiftCallBack(requestId,isFinish,msgText,rspMsg,data);
    }
}

void _HandleASRFailed(const char* requestId, int errorCode)
{
    if (HandleASRFailed_SwiftCallBack != NULL ){
        HandleASRFailed_SwiftCallBack(requestId,errorCode);
    }
}
AsrService.swift

@objc protocol AsrServiceDelegate {
    
    func asrSuccessed(requestId: String, isFinish: Bool, msgText: String, rspMsg: String, response: UniSendMsgResponse?)
    
    func asrFailed(requestId: String, errorCode: Int32)
}

class AsrService {
    
    weak var delegate: AsrServiceDelegate?
    
    init() {
        bridgingCFunction()
    }

    convenience init(delegate: AsrServiceDelegate) {
        self.init()
        self.delegate = delegate
    }
    
    fileprivate func bridgingCFunction() {
        HandleASRSuccessed_SwiftCallBack     = handleASRSuccessed
        HandleASRFailed_SwiftCallBack        = handleASRFailed
    }
    
    func startAsr() {
        StartAsr()
    }
    func stopAsr() {
        StopAsr()
    }

    fileprivate func handleASRSuccessed(requestId: UnsafePointer<Int8> , isFinish: Bool, msgText: UnsafePointer<Int8>, rspMsg: UnsafePointer<Int8>, msgResponseData: Data?) {
    
        var response: UniSendMsgResponse?
        if let `msgResponseData` = msgResponseData {
             response = UniSendMsgResponse.fromData(msgResponseData) as? UniSendMsgResponse
        }
        
        self.delegate?.asrSuccessed(requestId: requestId.stingValue, isFinish: isFinish, msgText: msgText.stingValue, rspMsg: rspMsg.stingValue, response: response)
    }
    
    fileprivate func handleASRFailed(requestId: UnsafePointer<Int8> , errorCode: Int32) {
        self.delegate?.asrFailed(requestId: requestId.stingValue, errorCode: errorCode)
    }
  
}

踩坑

MonoPInvokeCallback屬性無法找到

這個是舊版本 Unity 才有的一個屬性類,其實就是用來標(biāo)記這個函數(shù)會被 iOS 側(cè)反向調(diào)用的踏幻,也沒有什么實質(zhì)性的意義枷颊。但是新版本去掉了導(dǎo)致編譯不過。所以就在工程里面添加一個就行了该面。

// 參考這個帖子的說明 https://garry.tv/2018/02/15/steamworks-and-il2cpp/
internal class MonoPInvokeCallbackAttribute : Attribute
{
    public MonoPInvokeCallbackAttribute() { }
}

參考

Unity C#和iOS函數(shù)互相調(diào)用的方法

C# 委托

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末夭苗,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子隔缀,更是在濱河造成了極大的恐慌题造,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件猾瘸,死亡現(xiàn)場離奇詭異界赔,居然都是意外死亡丢习,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進(jìn)店門淮悼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來咐低,“玉大人,你說我怎么就攤上這事袜腥〖粒” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵羹令,是天一觀的道長鲤屡。 經(jīng)常有香客問我,道長福侈,這世上最難降的妖魔是什么酒来? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮癌刽,結(jié)果婚禮上役首,老公的妹妹穿的比我還像新娘。我一直安慰自己显拜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布爹袁。 她就那樣靜靜地躺著远荠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪失息。 梳的紋絲不亂的頭發(fā)上譬淳,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天,我揣著相機(jī)與錄音盹兢,去河邊找鬼邻梆。 笑死,一個胖子當(dāng)著我的面吹牛绎秒,可吹牛的內(nèi)容都是我干的浦妄。 我是一名探鬼主播,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼见芹,長吁一口氣:“原來是場噩夢啊……” “哼剂娄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起玄呛,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤阅懦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后徘铝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體耳胎,經(jīng)...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡惯吕,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了怕午。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片混埠。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诗轻,靈堂內(nèi)的尸體忽然破棺而出钳宪,到底是詐尸還是另有隱情,我是刑警寧澤扳炬,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布吏颖,位于F島的核電站,受9級特大地震影響恨樟,放射性物質(zhì)發(fā)生泄漏半醉。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一劝术、第九天 我趴在偏房一處隱蔽的房頂上張望缩多。 院中可真熱鬧,春花似錦养晋、人聲如沸衬吆。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逊抡。三九已至,卻和暖如春零酪,著一層夾襖步出監(jiān)牢的瞬間冒嫡,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工四苇, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留孝凌,地道東北人。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓月腋,卻偏偏與公主長得像蟀架,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子罗售,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,828評論 2 345

推薦閱讀更多精彩內(nèi)容

  • Lua 5.1 參考手冊 by Roberto Ierusalimschy, Luiz Henrique de F...
    蘇黎九歌閱讀 13,743評論 0 38
  • (譯注:P/Invoke辜窑,全稱是platform invoke service,平臺調(diào)用服務(wù)寨躁,簡單的說就是允許托管...
    IndieACE閱讀 3,324評論 0 7
  • C/C++輸入輸出流總結(jié) 前兩天寫C++實習(xí)作業(yè)穆碎,突然發(fā)現(xiàn)I/O是那么的陌生,打了好長時間的文件都沒有打開职恳,今天終...
    LuckTime閱讀 1,720評論 0 6
  • __block和__weak修飾符的區(qū)別其實是挺明顯的:1.__block不管是ARC還是MRC模式下都可以使用所禀,...
    LZM輪回閱讀 3,284評論 0 6
  • 史上最全的iOS面試題及答案 iOS面試小貼士———————————————回答好下面的足夠了----------...
    Style_偉閱讀 2,345評論 0 35