Unity进阶--C#补充知识点--【Unity跨平台的原理】Mono与IL2CPP
·来源于唐老狮的视频教学,仅作记录和感悟记录,方便日后复习或者查找
一.跨平台基本原理
知识回顾:
- ①在之前我们已经知道了跨语言的原理是.Net体系下定义了这些语言需要遵守的工业标准CLI。因此实现了面向.Net的语言都可以被编译转化成统一规范的CIL公共中间语言。
- ②而要实现下一步跨平台就需要我们把CIL再转化为对应平台的可执行机器码。
而对于第二步如何进行对CIL进行处理就有两种方式:Mono或者IL2CPP
二.Mono
关于Mono是什么不记得了可以回去看之前的笔记:Unity进阶--C#补充知识点--【Unity跨平台的原理】了解.Net
1.Unity与Mono的关系
Mono:
- 诞生于2004年
- 能够实现跨语言和跨平台两方面的功能
Unity:
- 公司诞生于2004
- Unity的底层是由C++实现的,但是希望上层有更方便易学的语言来提供编程
- 同时希望制作的工程游戏能够在多个平台上运行
综合Unity的需求和Mono的功能,我们可以很容易地看出Unity当时为什么会选用Mono:因为它同时满足了Unity需要跨语言和跨平台的需求
2.Unity跨平台的基本概念
Unity的两个主要部分:
Unity Engine(引擎部分):提供UnityEngine.dll动态库,各平台不同,由C/C++编写,包含平台相关代码,图形API,物理引擎,灯光等等所有游戏引擎的底层部分
Unity Editor(编辑器部分):提供UnityEditor.dll动态库,大部分由C#编写,用户的脚本最开始还可以用C#,JavaScrpit,Boo语言编写(不过最后因为C#太好用了成为主要语言),项目代码最后由Mono进行编译
Mono的组成:
①C#编译器(msc)
②Mono Runtime Mono运行时:类似于CLR公共语言运行时(虚拟机)
包含JIT(just in time)即时编译器,AOT(Ahead of time)提前编译器,GC,类库加载器等等
③BCL基础类库
④Mono类库
它提供了很多.Net之外的额外功能,主要用于构建操作系统上的应用
3.用Mono实现Unity跨平台的基本原理

①先把在Unity开发中使用的各种语言编译为CIL公共中间语言
②之后在对应平台上通过Mono VM(Mono虚拟机)把CIL转化为平台对应的机器码
4.用Mono实现Unity跨平台的优缺点
优点:
①理论上可以在无数个平台上实现无数个虚拟机
缺点:
①维护工作费时费力,每次版本更新的时候都要更新所有的Mono虚拟机
②低版本的Mono无法支持新版本C#的新特性
注意:Mono是JIT,即边编译边运行的。
Unity利用Mono实现上层平台的跨语言和跨平台,这是由Unity和Mono的组成结构决定的功能
三.IL2CPP
1.IL2CPP是什么
IL2CPP是继Mono之后Unity提供的又一个跨语言跨平台的解决方案。
我们可以在项目设置中把脚本后处理方法设置为IL2CPP
注意这个需要把该版本的Unity中安装对应平台的IL2CPP模块才可以使用他来打包工程
2.IL2CPP的工作原理
①IL2CPP顾名思义就是把IL中间语言转化为C++代码,
②因为各个平台基本上都有支持C++的优化过的编译器,所以可以再把C++编译为对应平台上对应的机器码。
③值得注意的是虽然变成了C++代码,但是内存管理还是采用C#的方式,因此这里仍然需要来一个IL2CPP VM虚拟机来进行内存的分配与管理以及线程创建等工作
3.IL2CPP和Mono的区别
Mono:
- ①打包速度快
- ②Mono是用JIT即时编译,支持更多类库
- ③必须把代码发布为托管程序集
- ④MonoVM虚拟机平台维护麻烦,且部分平台不支持(WebGL)
- ⑤由于Mono版本受限,因此C#的很多新特性无法使用
- ⑥IOS仅支持64位的Mono应用提交到应用商店
IL2CPP:
- ①打包速度略慢
- ②用AOT提前编译完成后才能运行(这使得运行速度更快)
- ③可以启用代码剥离功能减少工程大小
- ④多平台移植更方便
最大的区别就是:
- Mono是JIT边运行边编译,因此可以运行的时候动态生成代码和类型
- IL2CPP是AOT提前编译后运行,无法动态生成代码与类型,因此需要在编译的时候就完全确定好需要的类型(不过对于一些定义了的类型,但是没有具体显式调用过,但是在热更新的时候调用到了,那就会发生报错,具体的解决方案写在后面)
4.IL2CPP的优缺点
优点:
①运行速度快,效率高
②各个平台上方便维护
③因为有代码剥离打包出来的包会更小
缺点:
①可能会错误剥离一些代码(不过这个缺点是可以通过一些方法来弥补的)
②无法在运行时动态生成代码与类型
显然Mono有的主要优点它基本上也有,但是它又比Mono更容易维护且运行效率更高,因此一般能够用IL2CPP就用IL2CPP
5.IL2CPP缺点的弥补
在上面对IL2CPP的了解中,我们知道了IL2CPP的一些缺点与不足,接下来我们会尝试去弥补这些缺点。
5.1.解决类型裁剪
病因:在打包的时候会自动对Unity工程的DLL进行裁剪,将代码中没有用到的部分都给裁剪掉,以使得打包后的程序更小。但是它会不小心把一些看起来没有用到但是实际上是需要的类型给意外裁剪掉
症状:意外裁剪掉之后,在运行的时候我们会遇到【找不到这个类型】的错误
下药:
- 治标:IL2CPP处理模式的时候把代码剥离等级调为低
新版本好像还有个Minimal - 治本:通过在link.xml文件中告诉Unity哪些类型不能够被裁剪(一般搭配High等级的剥离代码使用以把打包的程序压缩到最小)。具体的使用方法如下所示
<?xml version="1.0" encoding="UTF-8"?><!--保存整个程序集--><assembly fullname="UnityEngine" preserve="all"/><!--没有“preserve”属性,也没有指定类型意味着保留所有--><assembly fullname="UnityEngine"/><!--完全限定程序集名称--><assembly fullname="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"><type fullname="Assembly-CSharp.Foo" preserve="all"/></assembly><!--在程序集中保留类型和成员--><assembly fullname="Assembly-CSharp"><!--保留整个类型--><type fullname="MyGame.A" preserve="all"/><!--没有“保留”属性,也没有指定成员 意味着保留所有成员--><type fullname="MyGame.B"/><!--保留类型上的所有字段--><type fullname="MyGame.C" preserve="fields"/><!--保留类型上的所有方法--><type fullname="MyGame.D" preserve="methods"/><!--只保留类型--><type fullname="MyGame.E" preserve="nothing"/><!--仅保留类型的特定成员--><type fullname="MyGame.F"><!--类型和名称保留--><field signature="System.Int32 field1" /><!--按名称而不是签名保留字段--><field name="field2" /><!--方法--><method signature="System.Void Method1()" /><!--保留带有参数的方法--><method signature="System.Void Method2(System.Int32,System.String)" /><!--按名称保留方法--><method name="Method3" /><!--属性--><!--保留属性--><property signature="System.Int32 Property1" /><property signature="System.Int32 Property2" accessors="all" /><!--保留属性、其支持字段(如果存在)和getter方法--><property signature="System.Int32 Property3" accessors="get" /><!--保留属性、其支持字段(如果存在)和setter方法--><property signature="System.Int32 Property4" accessors="set" /><!--按名称保留属性--><property name="Property5" /><!--事件--><!--保存事件及其支持字段(如果存在),添加和删除方法--><event signature="System.EventHandler Event1" /><!--根据名字保留事件--><event name="Event2" /></type><!--泛型相关保留--><type fullname="MyGame.G`1"><!--保留带有泛型的字段--><field signature="System.Collections.Generic.List`1<System.Int32> field1" /><field signature="System.Collections.Generic.List`1<T> field2" /><!--保留带有泛型的方法--><method signature="System.Void Method1(System.Collections.Generic.List`1<System.Int32>)" /><!--保留带有泛型的事件--><event signature="System.EventHandler`1<System.EventArgs> Event1" /></type><!--如果使用类型,则保留该类型的所有字段。如果类型不是用过的话会被移除--><type fullname="MyGame.I" preserve="fields" required="0"/><!--如果使用某个类型,则保留该类型的所有方法。如果未使用该类型,则会将其删除--><type fullname="MyGame.J" preserve="methods" required="0"/><!--保留命名空间中的所有类型--><type fullname="MyGame.SomeNamespace*" /><!--保留名称中带有公共前缀的所有类型--><type fullname="Prefix*" /></assembly></linker>
一般我们主要指定特定的命名空间或者特定的类不要被裁剪掉就行了
5.2.解决泛型问题
病因:IL2CPP在运行的时候无法动态生成类型和代码。
症状:如果打包生成前没有把之后没有想要使用的泛型类型显式地使用一次,那么这个泛型类型就不会被编译,这会导致之后使用没有被编译的类型就会出现【找不到类型】的报错
下药:
- ①泛型类:声明一个类,然后在这个类中声明一些public的泛型变量
- ②泛型方法:随便写一个静态方法,在这个泛型方法中调用一次这个泛型类型。(这个方法可以不被调用,这个只是为了告诉IL2CPP我们需要用这个泛型类型。
四.总结
①Unity的底层是C++,上层是C#等语言编写的,因此有跨语言的需求。Unity希望用户编写的工程程序能够在多个平台上运行,因此有跨平台的需求。而Mono与Unity诞生时间差不多,且能够满足跨语言和跨平台的需求,因此被Unity采用
②Mono是通过把语言都编译为CIL公共中间语言实现跨语言的操作(基于.Net Framework的功能),之后通过对应平台上的Mono VM(虚拟机)把CIL转化为对应平台的可执行机器码。
③Mono的优点在于打包快,理论上可以支持无限多的平台;缺点在于执行效率相对AOT的较慢(Mono是JIT的),维护各个平台的虚拟机麻烦,且每次更新版本都要同步更新全部平台的虚拟机
④IL2CPP是通过把语言编译为CIL之后通过IL2CPP应用程序转化为C++,然后再通过各个平台上都有的优化过的C++编译器编译为对应平台的机器码,不过由于内存管理还是采用C#的方式,因此还需要通过IL2CPP VM(虚拟机)去完成内存管理,线程分配等工作。
⑤IL2CPP优点是执行效率相比Mono更高(因为是AOT的),且会自动剥离无用的代码减小程序的体积。缺点是无法运行的时候生成类型与代码(因为是AOT),且可能会不小心剥离掉其实是需要的类的代码。
⑥对于IL2CPP的缺点,我们可以对应进行处理。
- 对于类型裁剪的问题,我们主要可以通过调低剥离程度或者使用link.xml指定不允许剥离哪些类
- 对于泛型的问题,我们可以需要的把泛型类和方法都定义在一个类中,并在一个静态方法中显式地调用它们一次
⑦在正常情况下,我们更推荐使用IL2CPP