前言:
其实小匹夫在U3D的开发中一直对U3D的跨平台能力很好奇。到底是什么原理使得U3D可以跨平台呢?后来发现了Mono的作用,并进一步了解到了CIL的存在。所以,作为一个对Unity3D跨平台能力感兴趣的U3D程序猿,小匹夫如何能不关注CIL这个话题呢?那么下面各位看官就拾起语文老师教导我们的作文口诀(Why,What,How),和小匹夫一起走进CIL的世界吧~
Why?
回到本文的题目,U3D或者说Mono的跨平台是如何做到的?
如果换做小匹夫或者看官你来做,应该怎么实现一套代码对应多种平台呢?
其实原理想想也简单,生活中也有很多可以参考的例子,比如下图(谁让小匹夫是做移动端开发的呢,只能物尽其用从自己身边找例子了T.T):
像这样一根线,管你是安卓还是ios都能充电。所以从这个意义上,这货也实现了跨平台。那么我们能从它身上学到什么呢?对的,那就是从一样的能源(电)到不同的平台(ios,安卓)之间需要一个中间层过度转换一下。
那么来到U3D为何能跨平台,简而言之,其实现原理在于使用了叫CIL(Common Intermediate Language通用中间语言,也叫做MSIL微软中间语言)的一种代码指令集,CIL可以在任何支持CLI(Common Language Infrastructure,通用语言基础结构)的环境中运行,就像.NET是微软对这一标准的实现,Mono则是对CLI的又一实现。由于CIL能运行在所有支持CLI的环境中,例如刚刚提到的.NET运行时以及Mono运行时,也就是说和具体的平台或者CPU无关。这样就无需根据平台的不同而部署不同的内容了。所以到这里,各位也应该恍然大了。代码的编译只需要分为两部分就好了嘛:
- 从代码本身到CIL的编译(其实之后CIL还会被编译成一种位元码,生成一个CLI assembly)
- 运行时从CIL(其实是CLI assembly,不过为了直观理解,不必纠结这种细节)到本地指令的即时编译(这就引出了为何U3D官方没有提供热更新的原因:在iOS平台中Mono无法使用JIT引擎,而是以Full AOT模式运行的,所以此处说的额即时编译不包括IOS)
What?
上文也说了CIL是指令集,但是不是还是太模糊了呢?所以语文老师教导我们,描述一个东西时肯定要先从外貌写起。遵循老师的教导,我们不妨先通过工具来看看CIL到底长什么样。
工具就是ildasm了。下面小匹夫写一个简单的.cs看看生成的CIL代码长什么样。
C#代码:
class Class1 { public static void Main(string[] args) { System.Console.WriteLine("hi"); } }
CIL代码:
.class private auto ansi beforefieldinit Class1 extends [mscorlib]System.Object { .method public hidebysig static void Main(string[] args) cil managed { .entrypoint // 代码大小 13 (0xd) .maxstack 8 IL_0000: nop IL_0001: ldstr "hi" IL_0006: call void [mscorlib]System.Console::WriteLine(string) IL_000b: nop IL_000c: ret } // end of method Class1::Main .method public hidebysig specialname rtspecialname instance void .ctor() cil managed { // 代码大小 7 (0x7) .maxstack 8 IL_0000: ldarg.0 IL_0001: call instance void [mscorlib]System.Object::.ctor() IL_0006: ret } // end of method Class1::.ctor } // end of class Class1
好啦。代码虽然简单,但是也能说明足够多的问题。那么和CIL的第一次亲密接触,能给我们留下什么直观的印象呢?
- 以“.”一个点号开头的,例如上面这份代码中的:.class、.method 。我们称之为CIL指令(directive),用于描述.NET程序集总体结构的标记。为啥需要它呢?因为你总得告诉编译器你处理的是啥吧。
- 貌似CIL代码中还看到了private、public这样的身影。姑且称之为CIL特性(attribute)。它的作用也很好理解,通过CIL指令并不能完全说明.NET成员和类,针对CIL指令进行补充说明成员或者类的特性的。市面上常见的还有:extends,implements等等。
- 每一行CIL代码基本都有的,对,那就是CIL操作码咯。小匹夫从网上找了一份汉化的操作码表放在附录部分,当然英文版的你的vs就有。
直观的印象有了,但是离我们的短期目标,说清楚(或者说介绍个大概)CIL是What,甚至是终极目标,搞明白Uniyt3D为何能跨平台还有2万4千9百里的距离。
好啦,话不多说,继续乱侃。
参照附录中的操作码表,对照可以总结出一份更易读的表格。那就是如下的表啦。
主要操作 | 操作数范围/条件 | 操作数类型 | 操作数 | |||||||||
缩写 | 全称 | 含义 | 缩写 | 全称 | 含义 | 缩写 | 全称 | 含义 | 缩写 | 全称 | 含义 | |
ld | load | 将操作数压到堆栈当中,相当于: push ax |
arg | argument | 参数 | ? | ? | 操作数中的数值 | .0 | ? | 第零个参数 | |
.1 | ? | 第一个参数 | ||||||||||
.2 | ? | 第二个参数 | ||||||||||
.3 | ? | 第三个参数 | ||||||||||
.s xx | (short) | 参数xx | ||||||||||
a | address | 操作数的地址 | 只有 .s xx,参见ldarg.s | |||||||||
loc | local | 局部变量 | 参见ldarg | |||||||||
fld | field | 字段(类的全局变量) | 参见ldarg | xx | ? | xx字段,eg: ldfld xx |
||||||
c | const | 常量 | .i4 | int 4 bytes | C#里面的int,其他的类型例如short需要通过conv转换 | .m1 | minus 1 | -1 | ||||
.0 | ? | 0 | ||||||||||
.1 | ? | 1 | ||||||||||
…… | ||||||||||||
.8 | 8 | |||||||||||
.s | (short) | 后面跟一个字节以内的整型数值(有符号的) | ||||||||||
? | ? | 后面跟四个字节的整型数值 | ||||||||||
.i8 | int 8 bytes | C#里面的long | ? | ? | 后面跟八个字节的整型数值 | |||||||
.r4 | real 4 bytes | C#里面的float | ? | ? | 后面跟四个字节的浮点数值 | |||||||
.r8 | real 8 bytes | C#里面的double | ? | ? | 后面跟八个字节的浮点数值 | |||||||
null | null | 空值(也就是0) | ? | ? | ? | ? | ? | ? | ||||
st | store | 计算堆栈的顶部弹出当前值,相当于: |