ILRuntime的实现原理
ILRuntime借助Mono.Cecil库来读取DLL的PE信息,以及当中类型的所有信息,最终得到方法的IL汇编码,然后通过内置的IL解译执行虚拟机来执行DLL中的代码。
IL托管栈和托管对象栈
为了高性能进行运算,尤其是栈上的基础类型运算,如int,float,long之类类型的运算,直接借助C#的Stack类实现IL托管栈肯定是个非常糟糕的做法。因为这意味着每次读取和写入这些基础类型的值,都需要将他们进行装箱和拆箱操作,这个过程会非常耗时并且会产生巨量的GC Alloc,使得整个运行时执行效率非常低下。
因此ILRuntime使用unsafe代码以及非托管内存,实现了自己的IL托管栈。
ILRuntime中的所有对象都是以StackObject类来表示的,他的定义如下:
struct StackObject |
通过StackObject这个值类型,我们可以表达C#当中所有的基础类型,因为所有基础类型都可以表达为8位到64位的integer。对于非基础类型而言,我们额外需要一个List来储存他的object引用对象,而Value则可以存储这个对象在List中的Index。由此我们就可以表达C#中所有的类型了。
托管调用栈
ILRuntime在进行方法调用时,需要将方法的参数先压入托管栈,然后执行完毕后需要将栈还原,并把方法返回值压入栈。
具体过程如下图所示
调用前: 调用完成后: |
函数调用进入目标方法体后,栈指针(后面我们简称为ESP)会被指向方法栈基址那个位置,可以通过ESP-X获取到该方法的参数和方法内部申明的局部变量,在方法执行完毕后,如果有返回值,则把返回值写在方法栈基址位置即可(上图因为空间原因写在了基址后面)。
当方法体执行完毕后,ILRuntime会自动平衡托管栈,释放所有方法体占用的栈内存,然后把返回值复制到参数1的位置,这样后续代码直接取栈顶部就可以取到上次方法调用的返回值了。