什么是IL2CPP?
在Unity4.6.1 p5以后版本中,在PlayerSettings—>Other Settings—>Scripting Backend有mono和il2cpp两个选项,它们是Unity脚本后处理(Scripting Backend)的两种方式。
IL2CPP是Unity一种新的脚本后处理(Scripting Backend)方式,针对.Net平台编译输出的IL(中间语言)进行处理。
IL2CPP的组成
- AOT静态编译编译器(il2cpp.exe)
- 运行时库(libil2cpp)
运行流程
AOT将IL转换为C++源码,再交给各平台的C++编译器进行编译,达到平台兼容的目的;
- C#代码编译为常规.NET DLL(托管程序集)
- 不属于脚本的所有托管程序集(如插件和基类库)由名为Unused Bytecode Stripper的Unity工具处理,该工具查找所有未使用的类和方法,并从这些DLL中删除它们(动态链接库)。此步骤显着减少了构建游戏的大小。
- 然后将所有托管程序集转换为标准C++代码。
- 生成的C++代码和IL2CPP的运行时库,使用编译器进行编译。
- 最后,代码链接到可执行文件。
AOT编译器
IL2CPP AOT编译器实际的执行文件是il2cpp.exe。在Windows平台你可以在Unity安装路径的Editor\Data\il2cpp目录下找到。对于OSX平台,它位于Unity安装路径的Contents/Frameworks/il2cpp/build目录内。 il2cpp.exe这个工具是一个托管代码可执行文件,其完全由C#写成。在开发IL2CPP的过程中,我们同时使用.NET和Mono编译器对其进行编译。
打包注意事项
IL2CPP和mono的最大区别就是不能在运行时动态生成代码和类型,所以这就要求必须在编译时就完全确定需要用到的类型。
类型剪裁
IL2CPP在打包时会自动对Unity工程的DLL进行裁剪,将代码中没有引用到的类型裁剪掉,以达到减小发布后ipa包的尺寸的目的。然而在实际使用过程中,很多类型有可能会被意外剪裁掉,造成运行时抛出找不到某个类型的异常。特别是通过反射等方式在编译时无法得知的函数调用,在运行时都很有可能遇到问题。
Unity提供了一个方式来告诉Unity引擎,哪些类型是不能够被剪裁掉的。具体做法就是在Unity工程的Assets目录中建立一个叫link.xml的XML文件,然后按照下面的格式指定你需要保留的类型:
1 | <linker> |
泛型实例
每个泛型实例实际上都是一个独立的类型,List和 List是两个完全没有关系的类型,这意味着,如果在运行时无法通过JIT来创建新类型的话,代码中没有直接使用过的泛型实例都会在运行时出现问题。
这个问题有两种方式,一个是使用CLR绑定,把用到的泛型实例都进行CLR绑定。另外一个方式是在Unity主工程中,建立一个类,然后在里面定义用到的那些泛型实例的public变量。这两种方式都可以告诉IL2CPP保留这个类型的代码供运行中使用。
对比
Mono
- 构建应用非常快。
- 由于Mono的JIT(Just In Time compilation)机制,所以支持更多托管类库。
- 支持运行时代码执行。
- 必须将代码发布成托管程序集(.dll文件,由mono或者.net生成)。
- Mono VM在各个平台移植异常麻烦,有几个平台就得移植几个VM(WebGL和UWP这两个平台只支持 IL2CPP)。
- Mono版本授权受限,C#很多新特性无法使用。
- iOS仍然支持Mono,但是不再允许Mono(32位)应用提交到Apple Store。
IL2CPP
- 可以调试生成的C++代码。
- 可以启用引擎代码剥离(Engine code stripping)来减少代码的大小。
- 程序的运行效率比Mono高,运行速度快。
- 多平台移植非常方便。
- 相比Mono构建应用慢。
- 只支持AOT(Ahead of Time)编译。
Reference
[1] Unity之IL2CPP