欢迎阅读指正和转载,但请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com
 
初探Unity3D的IL2CPP技术内幕01【翻译】
 
大概在一年以前,我们开始讨论U3D的脚本的未来。作为U3D开发团队的承诺,IL2CPP技术作为一种将会带来更高的性能,可移植性更好的虚拟机的脚本后端技术(script backend)将被引入到新版本的Unity3D中。2015年一月份,U3D开发团队发布了面向64位iOS平台的使用IL2CPP技术的第一个版本的Unity3D。U3D5则包含了使用了该技术的另一个平台——WebGL。感谢社区中大量的用户的不断测试和反馈,在IL2CPP推出之后,我们发布了很多针对IL2CPP运行期库和编译器的修正和优化补丁。
 
U3D开发团队将持续改善IL2CPP,但开发团队也认为:在不断持续向前进的同时,稍微迟缓一下开发的节奏,告知用户一些关于IL2CPP的内部工作细节,是很有意义的。因此,在接下来的一段时间时间内,U3D开发团队将会陆续地撰写一系列关于IL2CPP内部技术的相关文章。文章的主题计划如下:
 
1 基本概念——工具链和命令行参数(也就是本文)
2 生成的代码的概览
3 对生成的代码的一些调试技巧
4 函数调用(包括普通函数,虚函数等等)
5 泛型共享的实现
6 类型和方法的P/Invoke的包装
7 整合垃圾回收机制
8 测试框架和使用方法
 
为了能尽可能地完成这一系列的文章。我们将会讨论一些在未来肯定会发生变化的IL2CPP的实现细节。希望我们仍能提供一些有用有趣的信息。
 
IL2CPP是什么
 
我们所讨论的IL2CPP分为两大功能模块。分别是:
 
一个“事前”(ahead-of-time (AOT))编译器
一个用以支持虚拟机运作的运行期库
 
AOT编译器将会把.NET编译器编译CS源代码生成的中间语言(IL)进行编译,生成对应的C++源代码。运行期库则提供了一系列的功能服务以及一些抽象概念(abstractions),比如一个垃圾回收器,于平台无关的线程访问,文件操作,还有就是实现了一系列的内部调用(因为这些生成的原生C++代码将会直接操作修改托管的数据结构(managed data structure))
 
事前编译器
 
IL2CPP的事前编译器的可执行程序文件名为il2cpp,在Windows版本的U3D中它被安装在Editor\Data\il2cpp目录。在Mac OSX则安装在Contents/Frameworks/il2cpp/build目录下。il2cpp是一个用C#编写的程序。U3D开发团队已经分别使用.NET编译器和Mono编译器编译它。
 
il2cpp程序读入由Unity3D软件内置的Mono编译器生成的托管汇编代码(managed assmeblies),生成一系列的C++源代码。然后这些C++源代码将会被各个运行平台所使用的C++编译器编译。 IL2CPP工具链的示意图如下:
 
 
运行期库
 
IL2CPP技术的另一个部分就是用来支持VM运作的运行期库。U3D开发团队已经实现了这运行期库。这库的绝大部分功能是使用C++去实现的,千紫红一小部分依赖于所运行的操作系统的功能则是用了平台相关的汇编代码去实现的,对于用户而言可以不必关心这些平台相关的东西,由U3D开发团队去操心。这个运行期库文件命名为libil2cpp,它的运作方式是在编译生成项目产品时,以静态链接库的形式,被加入到U3D player可执行程序中去。IL2CPP技术的一个最关键的优点就是这个简单而且可移植的运行期库。
 
libil2cpp库的一些代码头文件已经随着Unity3D软件一起发布了,通过阅读这些头文件可以知道一些该库是如何去编写组织的。Windows版本的Unity3D这些头文件在 Editor\Data\PlaybackEngines\webglsupport\BuildTools\Libraries\libil2cpp\include目录下。在Mac OSX版本的则在Contents/Frameworks/il2cpp/libil2cpp目录下。例如,由il2cpp程序生成的C++代码,与libil2cpp运行库之间相互通信的接口方法,被定义在codegen/il2cpp-codegen.h文件中。
 
另一个运行期库的关键点则是垃圾回收器。U3D5的运行期库使用了基于Boehm-Demers-Weiser垃圾回收算法的libgc库作为底层实现。尽管如此,libil2cpp库被设计为可以运行用户使用其他的垃圾回收方案。例如U3D开发团队正在研究的一种整合了微软垃圾回收技术的方案,这方案是开源项目CoreCLR的一部分。在后续的文章中我们会更多讨论关于整合垃圾回收技术的课题
 
il2cpp程序是如何执行的
 
让我们看一段示例代码,我们使用Windows版本的Unity5.0.1,我们开启一个新的空U3D工程。然后我们添加一个用户脚本并且挂接在场景中的Main Camera game object,该脚本是要被导出成对应的C++代码的。这脚本里的类很简单,就是继承MonoBehaviour组件类。在Start必然事件方法中打印一句日志,代码入下:
 
using UnityEngine;

public class HelloWorld : MonoBehaviour
{
    void Start ()
    {
        Debug.Log("Hello, IL2CPP!");
    }
}
 
当我们把工程面向WebGL平台进行构建生成时,用进程观察器的可以看到一系列的程序命令行,这些命令行中就有启动il2cpp程序的调用,如下:
 
"C:\Program Files\Unity\Editor\Data\MonoBleedingEdge\bin\mono.exe" "C:\Program Files\Unity\Editor\Data\il2cpp/il2cpp.exe" --copy-level=None --enable-generic-sharing --enable-unity-event-support --output-format=Compact --extra-types.file="C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\Assembly-CSharp.dll" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\UnityEngine.UI.dll" "C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput"
 
这些命令行实在是有够长的,所以我们将它拆开来一步步分析,首先,Unity3D软件启动mono程序。如下
 
"C:\Program Files\Unity\Editor\Data\MonoBleedingEdge\bin\mono.exe"
 
Mono程序启动了il2cpp程序,如下:
 
"C:\Program Files\Unity\Editor\Data\il2cpp/il2cpp.exe"
 
注意,剩下的命令行参数都是传递给il2cpp程序的。而不是mono程序。我们一个个分析,首先,给il2cpp程序传递了5个标志:
 
命令行参数
参数描述
–copy-level=None 告诉il2cpp不要对这些生成的C++代码文件执行特定的文件拷贝操作
–enable-generic-sharing 启动泛型共享。这将告诉il2cpp只要有可能时,对泛型方法执行一个“共享”的操作。这将减少生成的C++代码的数量,同时也能减小生成的二进制代码的大小
–enable-unity-event-support 使用此开发,特别确保Unity的使用了反射技术的event机制能被支持。能正确地生成对应的C++代码。
–output-format=Compact 指定compact值会让生成的C++代码,尽可能地用更少的字符去命名数据类型和函数名。如果启用这个功能的话,生成的C++代码将会比较难去调试它。因为在IL代码中的数据类型名和函数名将不会原样地对应到这些C++代码中。但启用这个功能的话,因为C++编译器要编译的C++代码会少一些,所以也会编译得快一些。
–extra-types.file=”C:\Program Files\Unity\Editor\Data\il2cpp\il2cpp_default_extra_types.txt”

这个选项表示使用一个缺省的额外类型文件(default extra type file)。这个文件可以通过添加到Unity工程中,il2cpp程序通过此文件可以知道程序有哪些在IL代码中不可见,但是在运行时会出现的泛型类型或者是数组类型,将会被创建。需要特别说明的是,这个命令行参数在以后发布的IL2CPP的实现中,有可能会发生改变,目前我们还不确定该参数在什么时候什么版本上,能够有一个稳定的实现。

 
最后,我们在命令行中还可以看到两个文件和一个目录,如下
 
"C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\Assembly-CSharp.dll"
"C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\Managed\UnityEngine.UI.dll"
"C:\Users\Josh Peterson\Documents\IL2CPP Blog Example\Temp\StagingArea\Data\il2cppOutput"
 
il2cpp程序能接受一个列表。这列表上描述了所有的要转换成C++代码的IL代码文件名。在本工程中,这列表包括了我们自定义的Helloworld代码,Unity提供的Assembly-CSharp.dll,以及UnityEngine.UI.dll。注意,这个列表里面很显然缺失了一些所需的IL代码,比如,HelloWorld类肯定引用了UnityEngine.dll,而UnityEngine.dll则至少引用了mscorlib.dll。而且还可能引用了其他的中间代码库。这些库在哪里呢?事实上,il2cpp程序已经内部处理过了。当然在命令行上也可以显现出它们让用户明确知道它们其实是已被处理了,但这不是非常有必要的。Unity仅仅需要显式地提示那些并不会被其他代码所引用的中间代码文件。
 
il2cpp程序的最后一个命令行参数则是用来告诉这些C++代码文件将创建到哪个目录。如果你现在就感到好奇的话可以看看这个目录下的那些生成文件——其实这也是下一章的课题。在你观察这些文件之前,也许你想在WebGL build setting界面上勾选“Development Player”选项,这个选项如果被勾选了,那么在刚才输出的这些命令行中将会移除掉“–output-format=Compact”这个选项。也即是说将会在生成的C++代码中生成可读性更好的类型和方法名字
 
试一试在WebGL或者iOS的构建选项(Player Settings)界面中改变一下各种开关选项的值。你会看到不同的命令行选项将会被传递到il2cpp程序。例如在WebGL的PlayerSetting界面上改变“Enable Exceptions”选项的值为“Full”的话,将会给命令行参数中增加–emit-null-checks, –enable-stacktrace, a和 –enable-array-bounds-check三个参数选项。
 
IL2CPP不能做什么
 
我很乐意指出有什么事情是IL2CPP不能做到的。例如,我们是不计划为IL2CPP重新编写C#的标准库。当你使用IL2CPP技术去编译你的U3D工程的时候。所有的在mscorlib.dll,System.dll等等文件中的C#标准库的实现。将会和你不用IL2CPP技术去编译时,使用的是同一套代码。
 
我们之所以继续使用C#标准库的原有实现的原因是:C#标准库已经经过大量的用户测试和工程实现。已经是非常的健壮和高效了。正因为如此,所以当我们分析和IL2CPP技术有关的bug的时候,我们可以肯定这些bug要么发生在事前编译器,要么发生在运行期库中,不会发生在其他地方。
 
U3D团队如何开发,测试,发布IL2CPP
 
从2015年1月在Unity3D4.6.1p5版本中整合发布的第一个IL2CPP版本起,至今为止,针对从4.6版到5.0版本的Unity3D软件,我们已经发布了六个完整版本和七个补丁。我们已经修正了超过一百个在release note中提及的bug。
 
为了能够保证IL2CPP的持续改进。我们只维护了一个IL2CPP的开发版本。这个开发版本是基于Unity3D的用来发布alpha或者beta版本的主干版本。在每一个Unity3D的发布之前,我们将会把IL2CPP的修改应用到每一个发布分支,然后运行我们的测试程序,回归测试验证所有我们的在该版本修正的bug。我们的QA和技术支持团队已经以惊人的速度尽可能地完成这些工作。这意味着Unity3D用户将不会在超过一周的时间内,就可以得到最新的bug修正补丁。
 
我们的用户社区已经提交了非常多的高质量的bug报告,可见社区力量是非常宝贵的。对用户的五四反馈和帮助以改进IL2CPP的无私行为,Unity3D开发团队深感谢意。
 
IL2CPP技术开发团队有着非常强烈的“测试优先”的开发意识。我们经常雇佣有着测试驱动经验的员工,几乎不整合没有通过良好测试的需求到产品中。这样的一个开发策略在IL2CPP开发团队中执行效果很好。这意味着很多我们看到的bug,并不是表现异常,而是输入条件异常。例如,使用了一个64位的IntPtr,作为一个32位的数组索引,这将引起一个C++编译错误。这种差异将允许我们快速地修正bug。
 
通过社区的帮助,我们将努力工作,尽可能快地让IL2CPP越来越健壮。所以,如果你对这个开发工作倍感兴趣。欢迎加入我们团队(^_^)
 
上一章