思考并回答以下问题:
- 一个C#代码文件的生命周期,从编写到执行是怎样的?
- 什么是即时编译JIT?
- 什么是AOT?什么是Full AOT?
- 什么是静态编译和动态编译?有什么区别?
- 为什么CIL和具体的平台或者CPU无关?
在iOS平台上无法动态加载dll,为什么呢?提到这个问题就需要深入了解一些东西了,比如AOT、JIT、CIL、Mono等等。
Unity的Mono
Mono
Mono是微软.Net Framework的开源实现,基于C#语言的ECMA标准,包含C#编译器和CLR(Common Language Runtime,通用语言运行时)。
Mono让C#有了跨平台开发、运行的能力。
Mono为何能跨平台?
简而言之,其实现原理在于使用了叫CIL(Common Intermediate Language通用中间语言,也叫做MSIL微软中间语言)的一种代码指令集,CIL可以在任何支持CLI(Common Language Infrastructure,通用语言基础结构)的环境中运行,就像.NET是微软对这一标准的实现,Mono则是对CLI的又一实现。
由于CIL能运行在所有支持CLI的环境中,例如刚刚提到的.NET运行时以及Mono运行时,也就是说和具体的平台或者CPU无关。这样就无需根据平台的不同而部署不同的内容了。
所以到这里,你应该也明白Unity的跨平台能力,很大一部分是依靠Mono的跨平台能力(但不能否认Unity对Mono发展的贡献以及资源的跨平台处理上的能力)。
CIL编译过程
通过上面的描述,CIL代码的编译只需要分为两部分:
从代码本身到CIL的编译(其实之后CIL还会被编译成一种位元码,生成一个CLI assembly)
运行时从CIL(其实是CLI assembly,不过为了直观理解,不必纠结这种细节)到当前运行环境本地指令的提前编译(AOT)或即时编译(JIT)(这就引出了为何U3D官方没有提供热更新的原因:在iOS平台中Mono无法使用JIT引擎,而是以Full AOT模式运行的,所以此处说的即时编译不包括IOS)
JIT
JIT——Just in Time,即时编译。
从名字就能看的出来,即时编译,或者称之为动态编译,是在程序执行时才编译代码,解释一条语句执行一条语句,即将一条中间的托管的语句翻译成一条机器语句,然后执行这条机器语句。但同时也会将编译过的代码进行缓存,而不是每一次都进行编译。
所以可以说它是静态编译和解释器的结合体。不过你想想机器既要处理代码的逻辑,同时还要进行编译的工作,所以其运行时的效率肯定是受到影响的。因此,Mono会有一部分代码通过AOT静态编译,以降低在程序运行时JIT动态编译在效率上的问题。
不过一向严苛的iOS平台是不允许这种动态的编译方式的,这也是U3D官方无法给出热更新方案的一个原因。而Android平台恰恰相反,Dalvik虚拟机使用的就是JIT方案。
AOT
AOT——Ahead of Time,提前编译。
AOT编译在应用程序执行前进行,并且通常在不同计算机上(需要运行的平台)执行,用于转换成对应平台的指令。
其实Mono的AOT静态编译和JIT并非对立的。AOT同样使用了JIT来进行编译,只不过是被AOT编译的代码在程序运行之前就已经编译好了。不过还有一部分代码会通过JIT来进行动态编译。
简单总结一下AOT的过程:
- 收集要被编译的方法
- 使用JIT进行编译
- 发射(Emitting)经JIT编译过的代码和其他信息
- 直接生成文件或者调用本地汇编器或连接器进行处理之后生成文件。
Full AOT
上文也说了,iOS平台是禁止使用JIT的,可Mono的AOT模式仍然会保留一部分代码会在程序运行时动态编译。
为了破解这个问题,Mono提供了一个被称为Full AOT的模式。即预先对程序集中的所有CIL代码进行AOT编译生成一个本地代码映像,然后在运行时直接加载这个映像而不再使用JIT引擎。
**iOS无法dll热更新的根本原因**实际上,iOS并非直接把JIT禁止了,而是IOS封了JIT内存(或者堆)的可执行权限,相当于变相的封锁了JIT这种编译方式。
总结来说,代码是可以在iOS中动态生成的,只是iOS为了安全性的考虑,禁止了JIT内存的执行权限。因此在iOS平台上,Mono自己禁止了JIT这种编译模式,只能使用Full AOT(非Full AOT,即普通AOT编译模式下,部分代码也会使用JIT,所以必须Full AOT)的编译模式。
既然不能JIT的编译,那么我们就不能基于Assembly.Load来加载DLL的bytes了。
总结
讲了一些.Net编译的原理,让你更好的理解为什么在iOS平台上无法动态加载dll。