1. 程式人生 > >Unity將來時:IL2CPP怎麼用?

Unity將來時:IL2CPP怎麼用?

版本準備

前文詳細的介紹了IL2CPP的來龍去脈,這裡用一個實際的例子來看看Unity3D裡的IL2CPP都為我們做了哪些工作以及在使用的過程中會遇到哪些問題。

IL2CPP應用的第一個平臺是WebGL,為了讓遊戲可以一鍵部署到基於WebGL的瀏覽器中,Unity3D Script工作組的大牛們找到了一個絕妙的解決方案:不僅解決了C#,Unity Script語言相容問題,還解決了客戶端原始碼洩漏問題。這個功能在Unity5.0 Beta版中提供了測試。

IL2CPP的第二個試用平臺是iOS 64位版。大家都知道蘋果已經發了最後通牒,全新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版本來進行演示。


建立專案,加入程式碼
建立一個空的專案,加入兩個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()中,建立這個類的例項,並呼叫這兩個函式。這個原始碼可以讓我們研究以下幾個方面:
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++中的實現,最後發現是調到了IL2CPP的VM函式:

在這些C++的實現中,細心的讀者可能會發現他們時時刻刻都在使用MethodInfo和TypeInfo這樣的資訊。這個就是Unity Script專案組提到的Metadata。Metadata指的是非邏輯程式碼,而是函式,結構,變數以及類本省的一些資訊。比如名字,型別等。這個Metadata提供C++程式碼和後臺的IL2CPP VM執行時必要的資訊。

以上5條只是很簡單的例子,大家如果對IL2CPP的轉換感興趣,可以自己寫出想要了解的測試程式碼,然後再對比CPP檔案看其實現。

前方有坑,請小心
新的事物總是伴隨著問題,特別是在軟體行業,Bug是不可避免的。就目前階段而言,IL2CPP還有不少問題。這個就是專案中IL2CPPCompatible.cs存在的意義:做相容性測試。大家在實際的專案中如果遇到了問題,可以在這個檔案中追加測試程式碼。下面的表格把我們遇到的已知問題做一個列舉,供參考。

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++靜態語言的特性,我們不能使用諸如System.Reflection.Emit這樣的動態程式碼。(C# ATO方式編譯)

給使用Unity開發者的建議:
IL2CPP是大勢所趨,加上蘋果強制使用64位支援,意味著到了6月1號,所有用Unity開發的遊戲都要用到新的編譯方式。如果你的專案比較大,應該立刻開始嘗試IL2CPP,以便發現問題,並開始解決。

本文專案在:
CoconutIslandStudio/IL2CPP_Test · GitHub


歡迎關注IndieACE微信公眾號:IndieAce,可以看到IndieACE定期分享的好內容。

需要轉載IndieACE的文章請與我們私信聯絡。