转载请标明出处:http://www.cnblogs.com/zblade/
前面两篇文章从头到尾讲解了C#热更新的一些方案,从程序域来加载和卸载DLL,到使用ILRuntime来实现安卓和IOS平台的DLL热更新。文章二中讲解了ILRuntime对于IL虚拟机在加载DLL的过程中的一些解构。那么今天收尾的文章,就来讲解一下如何基于这个虚拟机实现对于类,方法,属性的调用。
一、基于appDomain来加载类实现反射的调用
对于ILRuntime中的反射,大概可以分为三种类型:一种是ILRuntime运行的DLL中,该程序集中的类之间的反射,这是基于C#的反射,应用无差别; 一种是unity游戏主工程中对DLL中类的反射,这样的反射可以分为两种,一种是获取类,然后直接调用方法, 一种是基于appDomain包装的反射,下面分别举例两种unity主工程对DLL中类的反射,来研究一下如何实现这个过程。
1、基于LoadedTypes来实现反射方法的调用
在ILRuntime中,不能基于System.Type来直接获取热更新DLL中的类,只有基于唯一的appDomain实例,基于LoadedTypes这种来获取热更新中的DLL,基于代码来分析,更为详细:
首先,加载获取该DLL中的指定类:
var it = appDomain.LoadedTypes["HotFix_Project.InstanceClass"]
跟踪LoadedTypes:
public Dictionary<string, IType> LoadedTypes{get{return mapType.InnerDictionary;}}
跟踪看mapType.InnerDictionary:
ThreadSafeDictionary<string, IType> mapType = new ThreadSafeDictionary<string, IType>();
这个mapType是什么时候装配的?
来自于文章二中的LoadAssembly的后续操作:
那么这个module.GetTypes是如何操作的?
分别基于协程来return type以及其nestedTypes,关键是看Types是怎么获取的:
关键是read操作:
继续跟进Read操作:
关键是:
var mtypes = metadata.Types
后续都是对其的封装和填充,对于metadata的填充,来自于InitializeTypeDefinitions这个操作:
关键操作是ReadType这个操作:
构建一个内部定义的类,然后做数据填充,看看关键的几个属性的设置:BaseType ,设置其父类型,fieldsrange/methods_range 是对属性范围和方法范围的设置:
所以基本方法还是ReadListRange:
在这儿,我们最终回到了文章二中对于IL虚拟机中的tableHeap的引用,最后实现了和文章二的首尾呼应。
好了,收起思绪,回到最开始的,获取类,这样获得的一个类,这样得到的一个类,继承自IType,在Unity主工程中,则需要System.Type才能继续使用反射接口,其对于的封装来自昱这个ILType封装的ReflectionType, 其中的ILRuntimeType继承自Type类:
基于其,可以直接调用System.Type的GetConstructor方法,构建实例,归并几个代码,可以表示为(直接使用的实例源代码):
var it = appDomain.LoadedTypes["HotFix_Project.InstanceClass"];
var type = it.ReflectionType;
var ctor = type.GetConstructor(new System.Type[0]);
var obj = ctor.Invoke(null);
对应可以得到DLL中该类的构造函数的调用:
2、基于appDomain内嵌的Invoke来实现反射
在ILRuntime中,在appDomain中内嵌了一套Invoke的实现,可以在Unity工程中直接调用来实现对热更新DLL中类的方法的调用:
关键操作就是2步: GetType和 GetMethod,获取类型的过程,和前面有点类似,就是对mapType中存储的获取,如果没有,则进行查找和填充,这儿重点说说方法是如何获取的:
粗看就是从methods中取出来,做相应的检查,如果通过则返回,那么初始化操作看看:
最后还是从definition.Methods中取出,逐个遍历其中的方法做一个分类存储,如果有静态构造函数,且满足对于的参数条件,则执行一次静态构造。
回到开始,在获取到类和方法的相关信息后,就可以执行对于的参数检验,然后执行反射:
可见,就是获取到一个IL的解释器,然后执行相应的反射,具体Run怎么执行,就不继续深入贴图了,有兴趣的可以持续跟踪(基本思路就是对stack的操作,塞入各个参数,然后执行一次操作,塞入结果,然后退回)
对于ILRuntime的反射基本就先研究到这儿,如果要应用到自己的项目中,可以继续深入研究一下代码,看看实现的具体细节。这儿附上开源的相关文档:
二、热更新DLL和Unity主工程的相互调用
基于前面的反射,我们可以基本理出热更DLL和unity主工程的交互本质: 基于IL虚拟机或者.net本身反射来实现交互,对于热更新DLL,其调用unity主工程,则主要是在热更新工程中添加对于unity工程的Assembly-CSharp的引用:
基于这个引用,可以调用其中类的各自方法,举两个类来测试:
一个不继承自MonoBehaviour:
一个继承自MonoBehaviour:
这两个Unity主工程中的类以及其中的方法,在热更新DLL中调用:
可以在Unity主工程中得到输出:
看一下track可以大概了解整个反射的执行过程。
对于Unity执行热更DLL中的调用,就是第一部分的反射实例。
总结:絮絮叨叨的写了三篇文章,算是对最近的研究做一个总结吧,现在项目还在评估这种热更新方案,基于稳妥,以及有基于slua的热更新项目用过,ILRuntime还在评估,其实本质都是相通的:基于自我实现的虚拟机(lua的虚拟机或者IL虚拟机,均基于栈实现),来构建一个自我运行环境,在里面解析执行对于的指令(lua虚拟机的指令/IL语句),来实现对热更新的代码(虽然是以资源方式热更新)。