Unity实现c#热更新方案探究(三)

时间:2023-03-08 17:19:54

转载请标明出处: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的后续操作:

Unity实现c#热更新方案探究(三)

那么这个module.GetTypes是如何操作的?

Unity实现c#热更新方案探究(三)

Unity实现c#热更新方案探究(三)

分别基于协程来return type以及其nestedTypes,关键是看Types是怎么获取的:

Unity实现c#热更新方案探究(三)

关键是read操作:

Unity实现c#热更新方案探究(三)

继续跟进Read操作:

Unity实现c#热更新方案探究(三)

关键是:

var mtypes = metadata.Types

后续都是对其的封装和填充,对于metadata的填充,来自于InitializeTypeDefinitions这个操作:

Unity实现c#热更新方案探究(三)

关键操作是ReadType这个操作:

Unity实现c#热更新方案探究(三)

构建一个内部定义的类,然后做数据填充,看看关键的几个属性的设置:BaseType ,设置其父类型,fieldsrange/methods_range 是对属性范围和方法范围的设置:

Unity实现c#热更新方案探究(三)

所以基本方法还是ReadListRange:

Unity实现c#热更新方案探究(三)

在这儿,我们最终回到了文章二中对于IL虚拟机中的tableHeap的引用,最后实现了和文章二的首尾呼应。

好了,收起思绪,回到最开始的,获取类,这样获得的一个类,这样得到的一个类,继承自IType,在Unity主工程中,则需要System.Type才能继续使用反射接口,其对于的封装来自昱这个ILType封装的ReflectionType, 其中的ILRuntimeType继承自Type类:

Unity实现c#热更新方案探究(三)

基于其,可以直接调用System.Type的GetConstructor方法,构建实例,归并几个代码,可以表示为(直接使用的实例源代码):

var it = appDomain.LoadedTypes["HotFix_Project.InstanceClass"];
var type = it.ReflectionType;
var ctor = type.GetConstructor(new System.Type[]);
var obj = ctor.Invoke(null);

对应可以得到DLL中该类的构造函数的调用:

Unity实现c#热更新方案探究(三)

2、基于appDomain内嵌的Invoke来实现反射

在ILRuntime中,在appDomain中内嵌了一套Invoke的实现,可以在Unity工程中直接调用来实现对热更新DLL中类的方法的调用:

Unity实现c#热更新方案探究(三)

关键操作就是2步: GetType和 GetMethod,获取类型的过程,和前面有点类似,就是对mapType中存储的获取,如果没有,则进行查找和填充,这儿重点说说方法是如何获取的:

Unity实现c#热更新方案探究(三)

粗看就是从methods中取出来,做相应的检查,如果通过则返回,那么初始化操作看看:

Unity实现c#热更新方案探究(三)

最后还是从definition.Methods中取出,逐个遍历其中的方法做一个分类存储,如果有静态构造函数,且满足对于的参数条件,则执行一次静态构造。

回到开始,在获取到类和方法的相关信息后,就可以执行对于的参数检验,然后执行反射:

Unity实现c#热更新方案探究(三)

可见,就是获取到一个IL的解释器,然后执行相应的反射,具体Run怎么执行,就不继续深入贴图了,有兴趣的可以持续跟踪(基本思路就是对stack的操作,塞入各个参数,然后执行一次操作,塞入结果,然后退回)

对于ILRuntime的反射基本就先研究到这儿,如果要应用到自己的项目中,可以继续深入研究一下代码,看看实现的具体细节。这儿附上开源的相关文档:

ILRuntime中的反射

二、热更新DLL和Unity主工程的相互调用

基于前面的反射,我们可以基本理出热更DLL和unity主工程的交互本质: 基于IL虚拟机或者.net本身反射来实现交互,对于热更新DLL,其调用unity主工程,则主要是在热更新工程中添加对于unity工程的Assembly-CSharp的引用:

Unity实现c#热更新方案探究(三)

基于这个引用,可以调用其中类的各自方法,举两个类来测试:

一个不继承自MonoBehaviour:

Unity实现c#热更新方案探究(三)

一个继承自MonoBehaviour:

Unity实现c#热更新方案探究(三)

这两个Unity主工程中的类以及其中的方法,在热更新DLL中调用:

Unity实现c#热更新方案探究(三)

可以在Unity主工程中得到输出:

Unity实现c#热更新方案探究(三)

看一下track可以大概了解整个反射的执行过程。

对于Unity执行热更DLL中的调用,就是第一部分的反射实例。

总结:絮絮叨叨的写了三篇文章,算是对最近的研究做一个总结吧,现在项目还在评估这种热更新方案,基于稳妥,以及有基于slua的热更新项目用过,ILRuntime还在评估,其实本质都是相通的:基于自我实现的虚拟机(lua的虚拟机或者IL虚拟机,均基于栈实现),来构建一个自我运行环境,在里面解析执行对于的指令(lua虚拟机的指令/IL语句),来实现对热更新的代码(虽然是以资源方式热更新)。