首发于IndieACE
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的文章请与我们私信联系。

发布于 2015-03-11 12:07