🌑

脑洞杂货铺

ILRuntimeNote

ILRuntime Note

简介

基本流程

  • 加载 hotfix dll,pdb 到内存流 => 得到 dll、pdb 内存流
    // 假设dll和pdb在“/”路径下
    MemoryStream hotfixDll = new MemoryStream(File.ReadAllBytes("/HotFix.dll"));
    MemoryStream hotfixPdb = new MemoryStream(File.ReadAllBytes("/HotFix.pdb"));
  • 用 dll 和 pdb 来初始化 ILRuntime 的 appdomain => 得到 appdomain 对象
    AppDomain appDomain = new AppDomain();
    appDomain.LoadAssembly(hotfixDll, hotfixPdb, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider());
  • 用 appdomain 做一些注册 用来优化性能或声明调用
  • 用 appdomain 来调用各种方法进行操作
    // 调用HotFix.dll中HotFix类中PrintLog静态方法,一个参数String。下面的null代表传递的对象实例,静态方法不需要传递,则为null
    appDomain.Invoke("HotFix.HotFix", "PrintLog", null, "MyHotFix print");

预热-热更Code快速上手

  • LoadHotfixFile() 加载热更dll
  • InitAppDomain() 初始化AppDomain

以上两步参考上面 基本流程 中代码示例。

  • InitILRuntime() 注册转换各种要用到的东西

  • MainPojCallHotFix() 主工程调用热更dll

    • 调用静态方法:

      • 调用静态无参数方法
      • 调用静态一个参数方法
      • 调用静态两个参数方法
    • 获取类:

    • 获取方法:

      • 获取方法1 - 获取无重载方法
      • 获取方法2 - 获取有形参重载的方法,创建形参重载的参数列表来获取方法,以区别是那个具体方法
    • 实例化类:

      • 实例化类型1 - 无缓存类,直接传递类名实例化
      • 实例化类型2 - 有缓存类,通过缓存类型实例化
    • 调用实例的成员方法:

    • 调用泛型方法:

    • 获取泛型方法:

    • 注册委托(主工程里声明委托,热更工程里将实例赋值给主工程委托,让主工程完成调用)

      • 如果主工程里没有注册相应的委托,并在主工程里调用了热更dll的委托,运行时会提示注册,并且给与相应注册的代码如下,加入到主工程里的注册阶段即可。
        KeyNotFoundException: Cannot find convertor for global::TestDelegateMethod
        Please add following code:
        appdomain.DelegateManager.RegisterDelegateConvertor<global::TestDelegateMethod>((act) =>
        {
            return new global::TestDelegateMethod((a) =>
            {
                ((Action<System.Int32>)act)(a);
            });
        });
    • 跨域继承(热更工程中继承主工程里的类,主工程可以调用实现的功能)

      • 必须在主工程中注册适配器才能用
      • 假设主工程中被继承的方法为InfoBase,定义如下
        using UnityEngine;
        namespace SmalBox.Auto
        {
            public abstract class InfoBase
            {
                public virtual int ID { get { return 0; } set { } }
                public virtual void Info(string str) { Debug.Log("Info str:" + str); }
                public abstract void Num(int num);
            }
        }
      • 注册适配器具体步骤 - 手写Adaptor脚本(假设主工程中被继承的方法为InfoBase)
        • 在主工程中添加UIBase的适配器类:InfoBaseAdapter
        • 让InfoBaseAdapter继承CrossBindingAdaptor
        • IDE中选择实现其三个抽象方法 BaseCLRTypeAdaptorTypeBaseCLRTypes,IDE会自动把重写框架写好。
        • 选择UIBaseAdapter重写其方法,生成CreateCLRInstance方法。
        • 生成Adaptor方法来写逻辑
        • 现在可以来写以上提到的生成出的5个必要的方法
          1. BaseCLRType
            // 返回主工程中 基类的类型
            public override Type BaseCLRType {get{return typeof(InfoBase);}}
          2. AdaptorType
            // 返回本类中 Adaptor的类型
            public override Type AdaptorType {get{return typeof(Adaptor);}}
          3. BaseCLRTypes
            // 只实现一个接口 返回null即可,实现多个参看官网文档
            public override Type[] BaseCLRTypes {get{return null;}}
            跨域继承只能有1个Adapter,因此应该尽量避免一个类同时实现多个外部接口,对于coroutine来说是IEnumerator,IEnumerator和IDisposable,ILRuntime虽然支持,但是一定要小心这种用法,使用不当很容易造成不可预期的问题日常开发如果需要实现多个DLL外部接口,请在Unity这边先做一个基类实现那些个接口,然后继承那个基类。
          4. CreateCLRInstance
            // 返回用 appdomain和instance 作为参数,调用下面的Adaptor的构造方法,
            public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) {return new Adaptor(appdomain, instance);}
          5. Adaptor
            public class Adapter : SmalBox.Auto.InfoBase, CrossBindingAdaptorType
            {
               // 创建空构造方法、有appdomain、instance参数的构造方法。
               ILTypeInstance instance;
               ILRuntime.Runtime.Enviorment.AppDomain appdomain;
               public Adapter(){}
               public Adapter(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
               {
                   this.appdomain = appdomain;
                   this.instance = instance;
               }
               // 返回IL实例
               public ILTypeInstance ILInstance { get { return instance; } }
            
               // 下面重写基类定义的内容
            
               // 重写ID 属性
               static CrossBindingFunctionInfo<System.Int32> mget_ID_0 = new CrossBindingFunctionInfo<System.Int32>("get_ID");
               static CrossBindingMethodInfo<System.Int32> mset_ID_1 = new CrossBindingMethodInfo<System.Int32>("set_ID");
               public override System.Int32 ID
               {
                  get
                  {
                      if (mget_ID_0.CheckShouldInvokeBase(this.instance))
                          return base.ID;
                      else
                          return mget_ID_0.Invoke(this.instance);
                  }
                  set
                  {
                      if (mset_ID_1.CheckShouldInvokeBase(this.instance))
                          base.ID = value;
                      else
                          mset_ID_1.Invoke(this.instance, value);
                  }
               }
               // 重写Info 虚方法
               static CrossBindingMethodInfo<System.String> mInfo_2 = new CrossBindingMethodInfo<System.String>("Info");
               public override void Info(System.String str)
               {
                   if (mInfo_2.CheckShouldInvokeBase(this.instance))
                       base.Info(str);
                   else
                       mInfo_2.Invoke(this.instance, str);
               }
               // 重写Num 抽象方法
               static CrossBindingMethodInfo<System.Int32> mNum_3 = new CrossBindingMethodInfo<System.Int32>("Num");
               public override void Num(System.Int32 num)
               {
                   mNum_3.Invoke(this.instance, num);
               }
            }
      • 注册适配器具体步骤 - 扩展编辑器自动生成Adaptor脚本 (上面手写太麻烦?ILRuntime给出一个编辑器扩展接口可以自动分析基类,生成其Adapter类ヽ(✿゚▽゚)ノ)
        using UnityEditor;
        using SmalBox.Auto;
        public class AutoCrossAdapter
        {
           [MenuItem("SmalBox/ILRuntime/生成跨域继承适配器")]
            static void GenerateCrossbindAdapter()
            {
                //由于跨域继承特殊性太多,自动生成无法实现完全无副作用生成,所以这里提供的代码自动生成主要是给大家生成个初始模版,简化大家的工作
                //大多数情况直接使用自动生成的模版即可,如果遇到问题可以手动去修改生成后的文件,因此这里需要大家自行处理是否覆盖的问题
                using(System.IO.StreamWriter sw = new System.IO.StreamWriter("Assets/MyTest/MainObj/Adapter/InfoBaseAdapter.cs")) // 这里设定要生成到哪里,脚本叫什么名字
                {
                   // 这句用GenerateCrossBindingAdapterCode,传入基类类型、命名空间名字 即可。
                    sw.WriteLine(ILRuntime.Runtime.Enviorment.CrossBindingCodeGenerator.GenerateCrossBindingAdapterCode(typeof(InfoBase), "SmalBox.Auto"));
                }
                // ...N多基类都写在这。。。
                // 在个编辑器脚本里如上加入所有基类,然后点击菜单上的这个按钮即可自动生成。
                // 自动生成出问题了再按照上面手写的来看看哪里有问题需要修复。
                AssetDatabase.Refresh();
            }
        }
    • CLR重定向

    • CLR绑定

      • CLR绑定借助了ILRuntime的CLR重定向机制来实现,因为实质上也是将对CLR方法的反射调用重定向到我们自己定义的方法里面来。
      • ILRuntime提供了一个代码生成工具来自动生成CLR绑定代码。
      • 编辑器扩展绑定
        [MenuItem("ILRuntime/Generate CLR Binding Code by Analysis")]
        static void GenerateCLRBindingByAnalysis()
        {
            //用新的分析热更dll调用引用来生成绑定代码
            ILRuntime.Runtime.Enviorment.AppDomain domain = new ILRuntime.Runtime.Enviorment.AppDomain();
            using (System.IO.FileStream fs = new System.IO.FileStream("Assets/StreamingAssets/HotFix_Project.dll", System.IO.FileMode.Open, System.IO.FileAccess.Read))
            {
                domain.LoadAssembly(fs);
        
                //Crossbind Adapter is needed to generate the correct binding code
                InitILRuntime(domain);
                ILRuntime.Runtime.CLRBinding.BindingCodeGenerator.GenerateBindingCode(domain, "Assets/ILRuntime/Generated");
            }
        
            AssetDatabase.Refresh();
        }
        
        static void InitILRuntime(ILRuntime.Runtime.Enviorment.AppDomain domain)
        {
            //这里需要注册所有热更DLL中用到的跨域继承Adapter,否则无法正确抓取引用
            domain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter());
            domain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
            domain.RegisterCrossBindingAdaptor(new TestClassBaseAdapter());
            domain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
        }
      • 主工程在ILRuntime全部初始化之后调用绑定
        ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain);
    • 主工程中调用热更工程中的协程

      • 使用Couroutine时,C#编译器会自动生成一个实现了IEnumerator,IEnumerator,IDisposable接口的类,因为这是跨域继承,所以需要写CrossBindAdapter
    • 主工程中反射获取dll中的类型

      var it = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
      var type = it.ReflectionType;

      热更Code

— 2021年3月17日