這篇文章給大家介紹Unity中的原生插件及平臺(tái)交互原理是什么,內(nèi)容非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對(duì)大家能有所幫助。
創(chuàng)新互聯(lián)專注于岷縣網(wǎng)站建設(shè)服務(wù)及定制,我們擁有豐富的企業(yè)做網(wǎng)站經(jīng)驗(yàn)。 熱誠(chéng)為您提供岷縣營(yíng)銷型網(wǎng)站建設(shè),岷縣網(wǎng)站制作、岷縣網(wǎng)頁(yè)設(shè)計(jì)、岷縣網(wǎng)站官網(wǎng)定制、成都小程序開發(fā)服務(wù),打造岷縣網(wǎng)絡(luò)公司原創(chuàng)品牌,更為您提供岷縣網(wǎng)站排名全網(wǎng)營(yíng)銷落地服務(wù)。
原生插件/平臺(tái)交互
雖然大多時(shí)候使用Unity3D進(jìn)行游戲開發(fā)時(shí),只需要使用C#進(jìn)行邏輯編寫。但有時(shí)候不可避免的需要使用和編寫原生插件,例如一些第三方插件只提供C/C++原生插件、復(fù)用已有的C/C++模塊等。有一些功能是Unity3D實(shí)現(xiàn)不了,必須要調(diào)用Android/iOS原生接口,比如獲取手機(jī)的硬件信息(UnityEngine.SystemInfo沒有提供的部分)、調(diào)用系統(tǒng)的原生彈窗、手機(jī)震動(dòng)等等
1 、C/C++插件
編寫和使用原生插件的幾個(gè)關(guān)鍵點(diǎn):
創(chuàng)建C/C++原生插件
導(dǎo)出接口必須是C ABI-compatible函數(shù)
函數(shù)調(diào)用約定
在C#中標(biāo)識(shí)C/C++的函數(shù)并調(diào)用
標(biāo)識(shí) DLL 中的函數(shù)。至少指定函數(shù)的名稱和包含該函數(shù)的 DLL 的名稱。
創(chuàng)建用于容納 DLL 函數(shù)的類??梢允褂矛F(xiàn)有類,為每一非托管函數(shù)創(chuàng)建單獨(dú)的類,或者創(chuàng)建包含一組相關(guān)的非托管函數(shù)的一個(gè)類。
在托管代碼中創(chuàng)建原型。使用 DllImportAttribute 標(biāo)識(shí) DLL 和函數(shù)。 用 static 和 extern 修飾符標(biāo)記方法。
調(diào)用 DLL 函數(shù)。像處理其他任何托管方法一樣調(diào)用托管類上的方法。
在C#中創(chuàng)建回調(diào)函數(shù),C/C++調(diào)用C#回調(diào)函數(shù)
創(chuàng)建托管回調(diào)函數(shù)。
創(chuàng)建一個(gè)委托,并將其作為參數(shù)傳遞給 C/C++函數(shù)。平臺(tái)調(diào)用會(huì)自動(dòng)將委托轉(zhuǎn)換為常見的回調(diào)格式。
確保在回調(diào)函數(shù)完成其工作之前,垃圾回收器不會(huì)回收委托。
那么C#與原生插件之間是如何實(shí)現(xiàn)互相調(diào)用的呢?
1.將源碼編譯為托管模塊;
2.將托管模塊組合為程序集;
3.加載公共語(yǔ)言運(yùn)行時(shí)CLR;
4.執(zhí)行程序集代碼。
注:CLR(公共語(yǔ)言運(yùn)行時(shí),Common LanguageRuntime)和Java虛擬機(jī)一樣也是一個(gè)運(yùn)行時(shí)環(huán)境,它負(fù)責(zé)資源管理(內(nèi)存分配和垃圾收集),并保證應(yīng)用和底層操作系統(tǒng)之間必要的分離。
為了提高平臺(tái)的可靠性,以及為了達(dá)到面向事務(wù)的電子商務(wù)應(yīng)用所要求的穩(wěn)定性級(jí)別,CLR還要負(fù)責(zé)其他一些任務(wù),比如監(jiān)視程序的運(yùn)行。按照.NET的說法,在CLR監(jiān)視之下運(yùn)行的程序?qū)儆?quot;托管"(managed)代碼,而不在CLR之下、直接在裸機(jī)上運(yùn)行的應(yīng)用或者組件屬于"非托管"(unmanaged)的代碼。
這幾個(gè)過程我總結(jié)為下圖:
圖.NET上的程序運(yùn)行
回調(diào)函數(shù)是托管代碼C#中的定義的函數(shù),對(duì)回調(diào)函數(shù)的調(diào)用,實(shí)現(xiàn)從非托管C/C++代碼中調(diào)用托管C#代碼。那么C/C++是如何調(diào)用C#的呢?大致分為2步,可以用下圖表示:
將回調(diào)函數(shù)指針注冊(cè)到非托管C/C++代碼中(C#中回調(diào)函數(shù)指委托delegate)
調(diào)用注冊(cè)過的托管C#函數(shù)指針
相比較托管調(diào)用非托管,回調(diào)函數(shù)方式稍微復(fù)雜一些。回調(diào)函數(shù)非常適合重復(fù)執(zhí)行的任務(wù)、異步調(diào)用等情況下使用。
由上面的介紹可以知道CLR提供了C#程序運(yùn)行的環(huán)境,與非托管代碼的C/C++交互調(diào)用也由它來完成。CLR提供兩種用于與非托管C/C++代碼進(jìn)行交互的機(jī)制:
平臺(tái)調(diào)用(Platform Invoke,簡(jiǎn)稱PInvoke或者P/Invoke),它使托管代碼能夠調(diào)用從非托管DLL中導(dǎo)出的函數(shù)。
COM 互操作,它使托管代碼能夠通過接口與組件對(duì)象模型 (COM) 對(duì)象交互??紤]跨平臺(tái)性,Unity3D不使用這種方式。
平臺(tái)調(diào)用依賴于元數(shù)據(jù)在運(yùn)行時(shí)查找導(dǎo)出的函數(shù)并封送(Marshal)其參數(shù)。下圖顯示了這一過程。
注意:
1. 除涉及回調(diào)函數(shù)時(shí)以外,平臺(tái)調(diào)用方法調(diào)用從托管代碼流向非托管代碼,而絕不會(huì)以相反方向流動(dòng)。雖然平臺(tái)調(diào)用的調(diào)用只能從托管代碼流向非托管代碼,但是數(shù)據(jù)仍然可以作為輸入?yún)?shù)或輸出參數(shù)在兩個(gè)方向流動(dòng)。
2. 圖中DLL表示動(dòng)態(tài)庫(kù),Windows平臺(tái)指.dll文件、Linux/Android指.so文件、Mac OS X指.dylib/framework文件、iOS中只能使用.a。后文都使用DLL代指,并且DLL使用C/C++編寫。
當(dāng)"平臺(tái)調(diào)用"調(diào)用非托管函數(shù)時(shí),它將依次執(zhí)行以下操作:
查找包含該函數(shù)的DLL。
將該DLL加載到內(nèi)存中。
查找函數(shù)在內(nèi)存中的地址并將其參數(shù)推到堆棧上,以封送所需的數(shù)據(jù)(參數(shù))。
將控制權(quán)轉(zhuǎn)移給非托管函數(shù)。
注意: 只在第一次調(diào)用函數(shù)時(shí),才會(huì)查找和加載 DLL并查找函數(shù)在內(nèi)存中的地址。iOS中使用的是.a已經(jīng)靜態(tài)打包到最終執(zhí)行文件中。
2、 Android插件
Java同樣提供了這樣一個(gè)擴(kuò)展機(jī)制JNI(Java NativeInterface),能夠與C/C++互相通信。
注:
JNI wiki-https://en.wikipedia.org/wiki/Java_Native_Interface 這里不深入介紹JNI,有興趣的可以自行去研究。如果你還不知道JNI也不用怕,就像Unity3D使用C/C++庫(kù)一樣,用起來還是比較簡(jiǎn)單的,只需要知道這個(gè)東西即可。并且Unity3D對(duì)C/C++橋接器這塊做了封裝,提供AndroidJNI/AndroidJNIHelper/AndroidJavaObject/AndroidJavaClass/AndroidJavaProxy方便使用等,具體使用后面在介紹。JNI提供了若干的API實(shí)現(xiàn)了Java和其他語(yǔ)言的通信(主要是C&C++)。從Java1.1開始,JNI標(biāo)準(zhǔn)成為java平臺(tái)的一部分,它允許Java代碼和其他語(yǔ)言寫的代碼進(jìn)行交互,保證本地代碼能工作在任何Java虛擬機(jī)環(huán)境下。"
作為知識(shí)擴(kuò)展,提一下Android Java虛擬機(jī)。Android的Java虛擬機(jī)有2個(gè),最開始是Dalvik,后面Google在Android 4.4系統(tǒng)新增一種應(yīng)用運(yùn)行模式ART。ART與Dalvik 之間的主要區(qū)別是其具有提前 (AOT) 編譯模式。 根據(jù) AOT 概念,設(shè)備安裝應(yīng)用時(shí),DEX 字節(jié)代碼轉(zhuǎn)換僅進(jìn)行一次。 相比于 Dalvik,這樣可實(shí)現(xiàn)真正的優(yōu)勢(shì) ,因?yàn)?Dalvik 的即時(shí) (JIT) 編譯方法需要在每次運(yùn)行應(yīng)用時(shí)都進(jìn)行代碼轉(zhuǎn)換。下文中用Java虛擬機(jī)代指Dalvik/ART。
C#/Java都可以和C/C++通信,那么通過編寫一個(gè)C/C++模塊作為橋接,就使得C#與Java通信成為了可能,如下圖所示:
注:C/C++橋接器本身跟Unity3D沒有直接關(guān)系,不屬于Android和Unity3D,圖中放在Unity3D中是為了代指libunity.so中實(shí)現(xiàn)的橋接器以表示真實(shí)的情況。
通過JNI既可以用于Java代碼調(diào)用C/C++代碼,也可用于C/C++代碼與Java(Dalvik/ART虛擬機(jī))的交互。JNI定義了2個(gè)關(guān)鍵概念/結(jié)構(gòu):JavaVM、JNIENV。JavaVM提供虛擬機(jī)創(chuàng)建、銷毀等操作,Java中一個(gè)進(jìn)程可以創(chuàng)建多個(gè)虛擬機(jī),但是Android一個(gè)進(jìn)程只能有一個(gè)虛擬機(jī)。JNIENV是線程相關(guān)的,對(duì)應(yīng)的是JavaVM中的當(dāng)前線程的JNI環(huán)境,只有附加(attach)到JavaVM的線程才有JNIENV指針,通過JNIEVN指針可以獲取JNI功能,否則不能夠調(diào)用JNI函數(shù)。
C/C++要訪問的Java代碼,必須要能訪問到Java虛擬機(jī),獲取虛擬機(jī)有2中方法:
在加載動(dòng)態(tài)鏈接庫(kù)的時(shí)候,JVM會(huì)調(diào)用JNI_OnLoad(JavaVM* jvm, void* reserved),第一個(gè)參數(shù)會(huì)傳入JavaVM指針。
在C/C++中調(diào)用JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args)創(chuàng)建JavaVM指針
所以,我們只需要在編寫C/C++橋接器so的時(shí)候定義JNI_OnLoad(JavaVM* jvm, void*reserved)方法即可,然后把JavaVM指針保存起來作為上下文使用。
獲取到JavaVM之后,還不能直接拿到JNI函數(shù)去獲取Java代碼,必須通過線程關(guān)聯(lián)的JNIENV指針去獲取。所以,作為一個(gè)好的開發(fā)習(xí)慣在每次獲取一個(gè)線程的JNI相關(guān)功能時(shí),先調(diào)用AttachCurrentThread();又或者每次通過JavaVM指針獲取當(dāng)前的JNIENV:java_vm->GetEnv((void**)&jni_env, version),一定是已經(jīng)附加到JavaVM的線程。通過JNIENV可以獲取到Java的代碼,例如你想在本地代碼中訪問一個(gè)對(duì)象的字段(field),你可以像下面這樣做:
1.對(duì)于類,使用jni_env->FindClass獲得類對(duì)象的引用
2.對(duì)于字段,使用jni_env->GetFieldId獲得字段ID
3.使用對(duì)應(yīng)的方法(例如jni_env->GetIntField)獲取字段的值
類似地,要調(diào)用一個(gè)方法,你
step1.得獲得一個(gè)類對(duì)象的引用obj,
step2.是方法methodID。這些ID通常是指向運(yùn)行時(shí)內(nèi)部數(shù)據(jù)結(jié)構(gòu)。查找到它們需要些字符串比較,但一旦你實(shí)際去執(zhí)行它們獲得字段或者做方法調(diào)用是非??斓摹tep3.調(diào)用jni_env->CallVoidMethodV(obj,methodID,args)。
從上面的示例代碼,我們可以看出使用原始的JNI方式去與Android(Java)插件交互是多的繁瑣,要自己做太多的事情,并且為了性能需要自己考慮緩存查詢到的方法ID,字段ID等等。幸運(yùn)的是,Unity3D已經(jīng)為我們封裝好了這些,并且考慮了性能優(yōu)化。Unity3D主要提供了一下2個(gè)級(jí)別的封裝來幫助高效編寫代碼:
注:Unity3D中對(duì)應(yīng)的C/C++橋接器包含在libunity.so中。
Level 1:AndroidJNI、AndroidJNIHelper,原始的封裝相當(dāng)于我們上面自己編寫的C# Wrapper。AndroidJNIHelper和AndroidJNI自動(dòng)完成了很多任務(wù)(指找到類定義,構(gòu)造方法等),并且使用緩存使調(diào)用java速度更快。AndroidJavaObject和AndroidJavaClass基于AndroidJNIHelper和AndroidJNI創(chuàng)建,但在處理自動(dòng)完成部分也有很多自己的邏輯,這些類也有靜態(tài)的版本,用來訪問java類的靜態(tài)成員。http://docs.unity3d.com/ScriptReference/AndroidJNI.html,http://docs.unity3d.com/ScriptReference/AndroidJNIHelper.html
Level 2:AndroidJavaObject、AndroidJavaClass、AndroidJavaProxy,這個(gè)3個(gè)類是基于Level1的封裝提供了更高層級(jí)的封裝使用起來更簡(jiǎn)單,這個(gè)在第三部分詳細(xì)介紹。
iOS編寫插件比Android要簡(jiǎn)單很多,因?yàn)镺bjective-C也是C-compatible的,完全兼容標(biāo)準(zhǔn)C語(yǔ)言。這些就可以非常簡(jiǎn)單的包一層 extern"c"{},用C語(yǔ)言封裝調(diào)用iOS功能,暴露給Unity3D調(diào)用。并且可以跟原生C/C++庫(kù)一樣編成.a插件。C#與iOS(Objective-C)通信的原理跟C/C++完全一樣:
除此之外,UnityiOS支持插件自動(dòng)集成方式。所有位于Asset/Plugings/iOS文件夾中后綴名為.m , .mm , .c ,.cpp的文件都將自動(dòng)并入到已生成的Xcode項(xiàng)目中。然而,最終編進(jìn)執(zhí)行文件中。后綴為.h的文件不能被包含在Xcode的項(xiàng)目樹中,但他們將出現(xiàn)在目標(biāo)文件系統(tǒng)中,從而使.m/.mm/.c/.cpp文件編譯。這樣編寫iOS插件,除了需要對(duì)iOSObjective-C有一定了解之外,與C/C++插件沒有差異,反而更簡(jiǎn)單。
關(guān)于Unity中的原生插件及平臺(tái)交互原理是什么就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,可以學(xué)到更多知識(shí)。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。
網(wǎng)站名稱:Unity中的原生插件及平臺(tái)交互原理是什么
分享網(wǎng)址:http://www.rwnh.cn/article34/ipcepe.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供定制開發(fā)、微信公眾號(hào)、網(wǎng)站策劃、小程序開發(fā)、手機(jī)網(wǎng)站建設(shè)、網(wǎng)站制作
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請(qǐng)盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如需處理請(qǐng)聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)