Unity3D將來(lái)時(shí):IL2CPP(下)
版本準(zhǔn)備
前文詳細(xì)的介紹了IL2CPP的來(lái)龍去脈,這里用一個(gè)實(shí)際的例子來(lái)看看Unity3D里的IL2CPP都為我們做了哪些工作以及在使用的過(guò)程中會(huì)遇到哪些問(wèn)題。
IL2CPP應(yīng)用的第一個(gè)平臺(tái)是WebGL,為了讓游戲可以一鍵部署到基于WebGL的瀏覽器中,Unity3D Script工作組的大牛們找到了一個(gè)絕妙的解決方案:不僅解決了C#,Unity Script語(yǔ)言兼容問(wèn)題,還解決了客戶端源碼泄漏問(wèn)題。這個(gè)功能在Unity5.0 Beta版中提供了測(cè)試。
IL2CPP的第二個(gè)試用平臺(tái)是iOS 64位版。大家都知道蘋(píng)果已經(jīng)發(fā)了最后通牒,全新App必須在15年2月1日支持64位CPU,而已經(jīng)上架的游戲也必須在15年6月1日更新的時(shí)候支持64位。這個(gè)64位編譯就是交由IL2CPP完成的。具體到版本是 Unity 4.6.1 p5,Unity4.6.2和Unity 4.6.2 p1。本文后面都使用4.6.2 p1版本來(lái)進(jìn)行演示。
創(chuàng)建項(xiàng)目,加入代碼
創(chuàng)建一個(gè)空的項(xiàng)目,加入兩個(gè)cs文件,一個(gè)叫IL2CPPCompatible.cs,另外一個(gè)是IL2CPPStudy.cs。前者主要用來(lái)測(cè)試代碼在IL2CPP中的兼容性,后者用來(lái)產(chǎn)生C++代碼,用來(lái)做對(duì)比分析。
以下是兩個(gè)文件的詳細(xì)內(nèi)容:
IL2CPPCompatible.cs
這個(gè)文件中有兩個(gè)兼容性測(cè)試函數(shù)。一個(gè)函數(shù)使用ThreadPool.QueueUserWorkItem啟動(dòng)一個(gè)新的線程。另一個(gè)則是SSL認(rèn)證函數(shù):ssl.AuthenticateAsClient (hosturl); 之所以寫(xiě)這兩個(gè)函數(shù)是因?yàn)樯鲜?.6.x IL2CPP對(duì)他們支持的還不是很好,會(huì)產(chǎn)生問(wèn)題,這個(gè)我們?cè)诤竺鏁?huì)詳細(xì)講到。
IL2CPPStudy.cs
using UnityEngine;
using System.Collections;
using System.IO;
using System.Threading;
public class CoconutClassStudy
{
public int inta;
public int intb;
public int Add()
{
return inta + intb;
}
public CoconutClassStudy(int a, int b)
{
inta = a;
intb = b;
}
public void IOTest(string filename)
{
if (File.Exists (filename)) {
FileStream fs = File.Open(filename, FileMode.Create);
fs.Close();
}
}
public void ThreadTest()
{
Thread a =new Thread(delegate(object state) {
Debug.Log ("Thread Started");
});
a.Start ();
}
}
public class IL2CPPStudy : MonoBehaviour {
// Use this for initialization
void Start () {
Debug.Log (CoconutFuncStudy (10 , 20));
CoconutClassStudy cc = new CoconutClassStudy (50, 60);
Debug.Log (cc.Add ());
cc.IOTest("test.txt");
cc.ThreadTest ();
Debug.Log (cc.GetType ());
}
// Update is called once per frame
void Update () {
}
int CoconutFuncStudy(int a, int b)
{
return a + b;
}
}
這個(gè)文件里面的內(nèi)容就更簡(jiǎn)單了:一個(gè)CoconutClassStudy類,里面有一個(gè)構(gòu)造函數(shù),一個(gè)Add函數(shù)和一個(gè)IOTest函數(shù)。另外在MonoBehaviour的Start()中,創(chuàng)建這個(gè)類的實(shí)例,并調(diào)用這兩個(gè)函數(shù)。這個(gè)源碼可以讓我們研究以下幾個(gè)方面:
1.cs的類在經(jīng)過(guò)IL2CPP以后如何在CPP文件中表達(dá)
2.C#的IO操作經(jīng)過(guò)IL2CPP以后如何在CPP文件中表達(dá)
3.當(dāng)調(diào)用new關(guān)鍵字在堆里產(chǎn)生一個(gè)實(shí)例的時(shí)候CPP文件又是如何做的
4.開(kāi)啟一個(gè)線程的操作IL2CPP會(huì)如何翻譯
5.調(diào)用cc.GetType()的行為IL2CPP如何處理
有了這連個(gè)文件后我們要做的第一件事情是生成XCode項(xiàng)目:
選擇IL2CPP編譯模塊,然后Build,生成XCode項(xiàng)目。
打開(kāi)項(xiàng)目,在項(xiàng)目結(jié)構(gòu)中打開(kāi)Classes目錄,可以看到多了一個(gè)Native的子目錄
IL2CPP轉(zhuǎn)換出的所有文件都在其中。
我們寫(xiě)的邏輯代碼,都在Assembly-CSharp.cpp中,除了這個(gè)文件,Native文件夾中還有很多以Bulk開(kāi)頭的文件,這些其實(shí)是IL2CPP把一些必要C#庫(kù)翻譯到CPP形成的文件。
像Bulk_Generics_x.cpp和System.Collections.Generic有關(guān)。
Bulk_UnityEngine.UI_x.cpp則和Unity自帶的UI有關(guān)。
讓我們粗略的分析下在CPP文件中前面的5條都是如何實(shí)現(xiàn)的:
1.cs的類在經(jīng)過(guò)IL2CPP以后如何在CPP文件中表達(dá)

我們的CoconutClassStudy類在CPP文件中變成了一個(gè)Struct,繼承于Object_t4。那這個(gè)Object_t4又是什么呢?
聰明的你一看注釋就知道了吧,沒(méi)錯(cuò),這個(gè)就是C#中的萬(wàn)物之源,System.Object。
既然我們C#的類變成了Struct,那類里面的函數(shù)都去哪里了呢?帶著這個(gè)疑問(wèn),我們來(lái)看第二條。
2.C#的IO操作經(jīng)過(guò)IL2CPP以后如何在CPP文件中表達(dá)
在CoconutClassStudy類中有一個(gè)成員函數(shù):IOTest。在CPP中,我們看到了如下的實(shí)現(xiàn):
類中的函數(shù)變成了一般的全局函數(shù),函數(shù)名字是類名加上函數(shù)名,最后加上一個(gè)后綴而成。而原本C#中的File.Exists和File.Open函數(shù)都有了相應(yīng)的C++實(shí)現(xiàn)。
3.當(dāng)調(diào)用new關(guān)鍵字在堆里產(chǎn)生一個(gè)實(shí)例的時(shí)候CPP文件又是如何做的?
C#代碼中我們?cè)赟tart函數(shù)中有一個(gè)顯示的New,找到相應(yīng)C++代碼:
可以看到代碼調(diào)用了一個(gè)叫il2cpp_codegen_object_new的函數(shù)。而這個(gè)函數(shù)最終調(diào)用了IL2CPP VM中的New函數(shù),分配了屬于GC管理的內(nèi)存。
接下來(lái)第四條
4.開(kāi)啟一個(gè)線程的操作IL2CPP會(huì)如何翻譯
C#中的System.Thread,在C++中是一個(gè)Thread_t26相當(dāng)巨大的結(jié)構(gòu),在New出了這個(gè)結(jié)構(gòu)之后,設(shè)置線程入口函數(shù),最后調(diào)用Thread_Start_m47啟動(dòng)線程。這個(gè)函數(shù)就是對(duì)應(yīng)System.Threading.Thread.Start()

我們看最后一條
5.調(diào)用cc.GetType()的行為IL2CPP如何處理。找到C++中的對(duì)應(yīng)Start函數(shù):
首先我們看到了對(duì)應(yīng)C#中的Type的C++實(shí)現(xiàn):Type_t28,其次是GetType()這個(gè)函數(shù)在C++中的實(shí)現(xiàn),最后發(fā)現(xiàn)是調(diào)到了IL2CPP的VM函數(shù):

在這些C++的實(shí)現(xiàn)中,細(xì)心的讀者可能會(huì)發(fā)現(xiàn)他們時(shí)時(shí)刻刻都在使用MethodInfo和TypeInfo這樣的信息。這個(gè)就是Unity Script項(xiàng)目組提到的Metadata。Metadata指的是非邏輯代碼,而是函數(shù),結(jié)構(gòu),變量以及類本省的一些信息。比如名字,類型等。這個(gè)Metadata提供C++代碼和后臺(tái)的IL2CPP VM運(yùn)行時(shí)必要的信息。
以上5條只是很簡(jiǎn)單的例子,大家如果對(duì)IL2CPP的轉(zhuǎn)換感興趣,可以自己寫(xiě)出想要了解的測(cè)試代碼,然后再對(duì)比CPP文件看其實(shí)現(xiàn)。
前方有坑,請(qǐng)小心
新的事物總是伴隨著問(wèn)題,特別是在軟件行業(yè),Bug是不可避免的。就目前階段而言,IL2CPP還有不少問(wèn)題。這個(gè)就是項(xiàng)目中IL2CPPCompatible.cs存在的意義:做兼容性測(cè)試。大家在實(shí)際的項(xiàng)目中如果遇到了問(wèn)題,可以在這個(gè)文件中追加測(cè)試代碼。下面的表格把我們遇到的已知問(wèn)題做一個(gè)列舉,供參考。
| 鏈接可執(zhí)行文件zlib報(bào)錯(cuò) | ThreadPool.QueueUserWorkItem運(yùn)行報(bào)錯(cuò) | SSL.AuthenticateAsClient運(yùn)行報(bào)錯(cuò) |
|
Unity 4.6.1 p5 | 發(fā)生 | 發(fā)生 | 發(fā)生 |
|
Unity 4.6.2 f1 | 發(fā)生 | 發(fā)生 | 發(fā)生 |
|
Unity 4.6.2 p1 | 修正 | 修正 | 發(fā)生 |
|
|
|
|
|
|
IL2CPP總結(jié)以及我們的建議
IL2CPP是Unity核心進(jìn)行的很重要的進(jìn)化之一。就現(xiàn)在來(lái)看,好處有以下幾點(diǎn):
1.運(yùn)行速度加快,游戲安裝尺寸減小,內(nèi)存占用降低
2.除了可以Mono調(diào)試C#之外,我們又多了一種選擇:Native C++ 源碼級(jí)調(diào)試(不知道你們什么感覺(jué),我對(duì)Unity C#調(diào)試頗有意見(jiàn),經(jīng)常連不上調(diào)試器,而且調(diào)試過(guò)程中常常宕機(jī)。換成用原生IDE調(diào)試C++代碼,就爽很多啦)。
3.可以快速的支持新的平臺(tái),當(dāng)然這點(diǎn)對(duì)我們關(guān)系不大。
帶來(lái)的問(wèn)題:
1.由于原來(lái)由Mono VM的IL代碼全部變成了CPP,導(dǎo)致項(xiàng)目中多了很多CPP代碼,編譯時(shí)間會(huì)顯著增加。
2.IL2CPP還有各種Bug,可能會(huì)導(dǎo)致原來(lái)的代碼不能很好的編譯運(yùn)行。需要等待Unity版本迭代。
3.鑒于C++靜態(tài)語(yǔ)言的特性,我們不能使用諸如System.Reflection.Emit這樣的動(dòng)態(tài)代碼。(C# ATO方式編譯)
給使用Unity開(kāi)發(fā)者的建議:
IL2CPP是大勢(shì)所趨,加上蘋(píng)果強(qiáng)制使用64位支持,意味著到了6月1號(hào),所有用Unity開(kāi)發(fā)的游戲都要用到新的編譯方式。如果你的項(xiàng)目比較大,應(yīng)該立刻開(kāi)始嘗試IL2CPP,以便發(fā)現(xiàn)問(wèn)題,并開(kāi)始解決。