Unity3D將來時:IL2CPP(下)
版本準備
前文詳細的介紹了IL2CPP的來龍去脈,這里用一個實際的例子來看看Unity3D里的IL2CPP都為我們做了哪些工作以及在使用的過程中會遇到哪些問題。
IL2CPP應用的第一個平臺是WebGL,為了讓游戲可以一鍵部署到基于WebGL的瀏覽器中,Unity3D Script工作組的大牛們找到了一個絕妙的解決方案:不僅解決了C#,Unity Script語言兼容問題,還解決了客戶端源碼泄漏問題。這個功能在Unity5.0 Beta版中提供了測試。
IL2CPP的第二個試用平臺是iOS 64位版。大家都知道蘋果已經發(fā)了最后通牒,全新App必須在15年2月1日支持64位CPU,而已經上架的游戲也必須在15年6月1日更新的時候支持64位。這個64位編譯就是交由IL2CPP完成的。具體到版本是 Unity 4.6.1 p5,Unity4.6.2和Unity 4.6.2 p1。本文后面都使用4.6.2 p1版本來進行演示。
創(chuàng)建項目,加入代碼
創(chuàng)建一個空的項目,加入兩個cs文件,一個叫IL2CPPCompatible.cs,另外一個是IL2CPPStudy.cs。前者主要用來測試代碼在IL2CPP中的兼容性,后者用來產生C++代碼,用來做對比分析。
以下是兩個文件的詳細內容:
IL2CPPCompatible.cs
這個文件中有兩個兼容性測試函數。一個函數使用ThreadPool.QueueUserWorkItem啟動一個新的線程。另一個則是SSL認證函數:ssl.AuthenticateAsClient (hosturl); 之所以寫這兩個函數是因為上述4.6.x IL2CPP對他們支持的還不是很好,會產生問題,這個我們在后面會詳細講到。
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;
}
}
這個文件里面的內容就更簡單了:一個CoconutClassStudy類,里面有一個構造函數,一個Add函數和一個IOTest函數。另外在MonoBehaviour的Start()中,創(chuàng)建這個類的實例,并調用這兩個函數。這個源碼可以讓我們研究以下幾個方面:
1.cs的類在經過IL2CPP以后如何在CPP文件中表達
2.C#的IO操作經過IL2CPP以后如何在CPP文件中表達
3.當調用new關鍵字在堆里產生一個實例的時候CPP文件又是如何做的
4.開啟一個線程的操作IL2CPP會如何翻譯
5.調用cc.GetType()的行為IL2CPP如何處理
有了這連個文件后我們要做的第一件事情是生成XCode項目:
選擇IL2CPP編譯模塊,然后Build,生成XCode項目。
打開項目,在項目結構中打開Classes目錄,可以看到多了一個Native的子目錄
IL2CPP轉換出的所有文件都在其中。
我們寫的邏輯代碼,都在Assembly-CSharp.cpp中,除了這個文件,Native文件夾中還有很多以Bulk開頭的文件,這些其實是IL2CPP把一些必要C#庫翻譯到CPP形成的文件。
像Bulk_Generics_x.cpp和System.Collections.Generic有關。
Bulk_UnityEngine.UI_x.cpp則和Unity自帶的UI有關。
讓我們粗略的分析下在CPP文件中前面的5條都是如何實現的:
1.cs的類在經過IL2CPP以后如何在CPP文件中表達

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

我們看最后一條
5.調用cc.GetType()的行為IL2CPP如何處理。找到C++中的對應Start函數:
首先我們看到了對應C#中的Type的C++實現:Type_t28,其次是GetType()這個函數在C++中的實現,最后發(fā)現是調到了IL2CPP的VM函數:

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