Mono

  Unity最大的一个特点是一次制作,多平台部署。在Unity推出IL2CPP的时候,这一核心功能是靠Mono实现的,可以说Mono是Unity跨平台的根本所在。在说Mono之前,我们不得不先提一下C#和其背后的.Net Framework。C#是微软推出的一种基于.NET框架的、面向对象的高级编程语言,它由C语言和C++派生而来,继承了其强大的性能,同时又以.NET框架类库作为基础,拥有类似Visual Basic的快速开发能力。

  C#并不被编译成为能够直接在计算机上执行的二进制本地代码。与Java类似,它被编译成为中间代码(Intermediate Language),然后通过.NET Framework提供的的虚拟机(也被称为通用语言运行库,Common Language Runtime,简称CLR)执行。微软公司已经向ECMA申请将C#作为一种标准。在2001年12月,ECMA发布了ECMA-334 C#语言规范。C#在2003年成为一个ISO标准(ISO/IEC 23270)。

  然而C#虽然很强大,但是因为微软的.Net Framework只能在Windows上使用切不开源,因此一直被人诟病不能够跨平台。在这个背景下,Mono出现了。Mono是一个由Xamarin公司(先前是Novell,最早为Ximian)所主持的自由开放源代码项目。该项目的目标是创建一系列匹配ECMA标准(Ecma-334和Ecma-335)的.NET工具,包括C#编译器和通用语言架构。与微软的.NET Framework(Common Language Runtime)不同,Mono使用的Mono VM作为运行时库,不仅可以运行于Windows系统上,还可以运行于Linux,FreeBSD,Unix,OS X和Solaris,甚至可以运行在一些游戏平台上。可以说,Mono使得C#这门语言有了很好的跨平台能力。


中间代码(Intermediate Language)

  前面提到,C#跟Java类似,并不被编译成为能够直接在计算机上执行的二进制本地代码,而是被编译成为交由虚拟机执行的中间代码(Intermediate Language,简称IL)。对于那些在.Net Framework虚拟机上执行的IL,我们一般特称其为CIL(Common Intermediate Language)ILCIL并没有本质上的区别。接着,IL(CIL)交由虚拟机执行,然后生成Native Code。

.Net Framework执行C#脚本的过程
.Net Framework执行C#脚本的过程

从上面可以看到,因为有了IL虚拟机这两个概念,我们不难发现讨论使用哪种高级编程语言来创建IL已经失去了意义,我们只需要提供能将其翻译成ILCompiler,就能够让其在虚拟机上运行。此外,正是由于引入了虚拟机,才使得很多动态代码特性得以实现。通过虚拟机我们甚至可以由代码在运行时生成新代码并执行,这个是静态编译语言所无法做到的。


IL2CPP

  介绍完IL之后,我们再回头看IL2CPP。顾名思义,我们不难知道IL2CPP(Intermediate Language To C++)就是将IL转换为C++代码。根据Unity对IL2CPP的介绍IL2CPP包含两个功能模块:

  • An ahead-of-time (AOT) compiler
  • A runtime library to support the virtual machine

其工作流程如下图所示:

IL2CPP执行C#脚本的过程
IL2CPP执行C#脚本的过程

可以看到,与Mono不同的是,在得到IL之后,IL2CPP提供的AOT compiler(il2cpp.exe)先将其转换为C++代码,然后交给各个平台的C++编译器直接编译成能执行的原生汇编代码。前面我们已经分析过,使用IL虚拟机的模式可以获得很多静态编译语言所没有的动态代码特性。那为什么IL2CPP还要花费时间把已经生成的IL转换为C++代码呢?根据Unity官方的一篇博客The future of scripting in Unity所说,使用IL2CPP,主要有以下几个原因:

  • Unity使用的Mono版权受限,很多C#的新特性无法使用
  • Mono的跨平台是通过Mono VM(虚拟机)实现的,Unity支持几个平台,就要维护几个VM,费时费力
  • 使用C++代码,会使得程序的运行效率提升

之所以将il2cpp.exe称为AOT compiler,是因为它会生成静态语言C++。区别于生成虚拟机可执行的字节码的JIT(Just In Time)编译方式,IL2CPP采用的AOT(Aahead Of Time)编译方式会将程序全部生成为机器码(Native Code),那为什么IL2CPP还需要提供一个支持Virtual MachineRuntime Library呢?

  我们知道,类似C#这种动态语言,所有的内存分配和回收都由一个叫做GC(Garbage Collector)的组件完成。尽管IL2CPP将C#代码变成了静态的C++,但是内存管理这块还是遵循C#的方式使用GC完成。因此,IL2CPP集成了一个使用C++实现的Runtime Library(有时也被称为IL2CPP VM),用于在运行时提供垃圾回收、线程管理等功能。需要注意的是,IL2CPP VM并不直接内嵌某个具体的GC组件,而是通过调用API的方式来实现垃圾回收的功能。


选择Mono还是IL2CPP?

  在了解了MonoIL2CPP之后,我们可以知道,MonoIL2CPP的工作流程大致如下所示:

Mono
Mono

IL2CPP
IL2CPP

比较之下,MonoIL2CPP之间的优缺点点主要有:

  • Mono使用JIT编译方式,支持很多动态语言特性;IL2CPP使用AOT编译方式,但很多平台出于安全的考虑是不允许JIT的,例如iOS平台
  • Mono只需要生成IL,打包时间较快;而IL2CPP处理要使用Mono Compiler生成IL,还需要将IL转换为C++代码,因此打包时间较慢
  • Mono使用动态语言C#,Mono VM需要执行IL加载和动态解析的工作,程序执行速度较慢;IL2CPP使用静态语言C++,IL2CPP VM不需要执行IL加载和动态解析的工作,程序执行速度较快
  • IL2CPP可以通过代码优化的方式来减少可执行程序的大小

总的来说,在开发阶段和最后发布阶段使用建议使用IL2CPP进行打包,而在迭代阶段,为了提高迭代速度,我们可以使用打包速度较快的Mono