1、Unity引擎是如何做到跨平台运行的
我们从基础概念开始,一步步走进真相:
程序集
一个.Net程序由一个或者多个程序集组成,程序集可以是库,也可以是应用程序。.Net中,库项目导出的文件格式为.dll,应用程序导出后的文件格式为.exe。
类似Flash项目,也是由库项目和应用程序项目组成。在Flash中,库项目导出后的文件格式为.swc,应用程序导出后的文件格式为.swf。
比如:一般Unity项目里包含这四个C#库项目:
vs编译之后,然后我们在项目目录的缓存目录Library\ScriptAssemblies下,能找到对应的四个dll文件:
CIL(Common Language Runtime)
刚才我们提到C#库项目导出之后生成的是一个DLL文件, 这个DLL文件里,存放的是CIL代码集。
它是微软在发布.Net框架的时候,一起发布的一个语言。CIL语言大概长这个样子:
它原本的C#程序是这样:
在.Net平台下,所有的语言(C#,VB.NET等)都会被编译成CIL,然后运行在虚拟机上,由虚拟机把CIL转换成各个平台的原生码(CPU可直接执行的代码)。
是不是又有点像Flash,Flash程序也是运行在虚拟机上的。
虚拟机——Mono
大家在Unity项目中都见过Mono这个词,MonoDeveloper,MonoBehavior等,那Mono到底是个什么东西?
Mono是一个基于CLR的开源项目,允许引擎和用户的托管代码运行在每一个目标平台上。而CLR全称为通用语言运行平台(Common Language Runtime),是微软的.Net虚拟机。它其中两个主要作用是:1,编译——运行前把C#编译为CIL;2,运行——在运行的时把CIL转换成各平台的原生码。
这些是Mono支持的平台:Android,Apple iOS(iOS,Apple macOS,Apple tvOS,Apple watchOS),BSD (OpenBSD, FreeBSD, NetBSD),Linux,Microsoft Windows,Nintendo Wii,Sony PlayStation 3,Sony PlayStation 4,Sun Solaris
IL2CPP
刚才我们已经知道了Unity跨平台是如何做到跨平台的:
1、把C#通过Mono compiler(其他语言用的是Unity单独开发的一个Unity compiler),编译成CIL语言;
2、各个平台下的Mono虚拟机,运行CIL语言,转换成原生码给CPU执行。
其实Unity后来出了另外一个方案:IL2PP。
它会在项目转成CIL之后,再把CIL转成CCPP,然后在运行的时候,把CPP加载进来,由各个平台的IL2PP VM转换成原生码。
Mono vs IL2CPP
Mono模式下编译出来的安卓包体:
libmono.so包含了mono VM的功能
IL2CPP模式下编译出来的安卓包体:
IL2CPP的好处是:
1、运行速度快(CPP转原生码比CIL快);
2、减少Unity公司的维护成本:Mono VM官方是不支持那么多平台的,所以很多平台的Mono VM都需要Unity自己去制作和维护,而C++编译器是本来各个平台都现成的。
缺点是:
1、包体会变大;
3、不支持JIT。
2、Mono如何运行CIL
这个部分将会告诉大家,IOS平台究竟有和特别之处,为什么在它上面实现代码热更新那么麻烦。
1、JIT(Just In Time)模式——在编译的时候,把C#编译成CIL,在运行时,逐条读入,逐条解析翻译成原生码交给CPU再执行;
2、AOT(Ahead Of Time)模式——在编译成CIL之后,会把CIL再处理一遍,编译成原生码,在运行的时候交给CPU直接执行,Mono下的AOT只会处理部分的CIL,还有一部分CIL采用了JIT的模式;
3、Full AOT模式——在编译成CIL之后,把所有的CIL编译成原生码,在运行的时候直接执行。
(留个坑:AOT模式下,编译出来的内容有两部分:原生码和CIL,它们是如何存放的,找了好多资料,没查到,本地想测试,但是环境暂时搭不起来,这个后面和IL2CPP一起研究)
Windows和Android系统采用的是JIT模式,那IOS用的哪一种呢?
我们看到,JIT模式是边运行,边翻译,支持运行时加载新的代码进来。而IOS是禁止内存的可执行权限的,不允许在运行过程中创建新的函数等,所以必须要提前编译好所有的代码,更不用说在运行时加载新的代码了。就是说,在IOS下,Mono采用的是Full AOT模式运行CIL。