14.CLR重定向和CLR绑定

  1. 14.更多跨域调用-CLR重定向和CLR绑定
    1. 14.1 知识点
      1. 复制之前的主入口调用
      2. 复习 CLR是什么
        1. CLR(Common Language Runtime)
      3. CLR重定向是什么
      4. CLR绑定的作用和原理
      5. 如何进行CLR绑定
        1. CLR绑定步骤概述
        2. 打开ILRuntimeCLRBinding脚本,在InitILRuntime函数添加我们注册适配器的代码。添加了之后等一下点击才能生成我们跨域继承适配器的绑定类。
        3. 点击 工具栏——>ILRuntime——>通过自动分析热更DLL生成CLR绑定,执行的其实就是ILRuntimeCLRBinding脚本的GenerateCLRBindingByAnalysis方法。此时就可以在ILRuntimeCLRBinding.cs 代码中设置的导出路径中看到生成的绑定代码。我们自己写的适配器代码绑定也被生成了。
        4. 在ILRuntimeManager的InitILRuntime方法中添加注册CLR绑定信息的逻辑
      6. CLR绑定性能提升测试
        1. 测试思路
        2. 在Unity工程中写一个静态测试函数
        3. 在ILRuntime工程进行十万次调用测试,重新生成代码
        4. 运行Unity,测试十万次循环的事件。注意,现在直接在Unity运行是不会优化性能的。因为我们是再之前绑定后,新加的代码。之前绑定时还没有TestFun这个方法,在IlRuntime工程也没有调用。
        5. 重新点击CLR绑定。会分析有哪些使用的方法和类。可以看到因为我们用了Lesson14脚本所以也被绑定了,内部绑定的是TestFun方法。因为在IlRuntime工程调用了。
        6. 重新运行优化性能后的代码,性能得到显著提升。
      7. 自定义CLR重定向
        1. 自定义CLR重定向概述
        2. 主要目标是,让Unity的Log走进我们自己写的Log函数中,在我们的Log中能打印在热更工程行号信息。现在我们直接运行是看不到的行号的。
        3. 打开CLR绑定UnityDebug相关脚本,可以看到CLR绑定是通过反射得到方法,并替换成下方的自己Log0方法
        4. 我们可以拷贝Log_0方法到我们Ilruntime管理器,再原绑定函数的基础上修改,改方法名改成MyLog,改成非静态的方法。AutoList改成List。注意因为用到指针相关,要用到unsafe关键字。原Debug绑定脚本没使用unsafe修饰是因为他一整个类都是unsafe修饰的,unsafe class UnityEngine_Debug_Binding。使用__domain.DebugService.GetStackTrace(__intp);获取行号,打印时拼接了行号信息。
        5. 查看自动生成的绑定脚本UnityEngine_Debug_Binding的重定向代码逻辑,准备进行模仿。在Ilruntime管理器的InitILRuntime方法中,要在注册CLR绑定信息前,进行CLR重定向MyLog中。使用反射得到Debug的Type,再得到Debug中Log方法的方法信息进行重定向。模仿UnityEngine_Debug_Binding的重定向代码逻辑即可。
        6. 现在可以看到我们在热更工程第几行打印信息的。
      8. 总结
    2. 14.2 知识点代码
      1. ILRuntimeCLRBinding
      2. Lesson14_更多跨域调用_CLR重定向和CLR绑定
      3. UnityEngine_Debug_Binding
      4. ILRuntimeManager
      5. ILRuntimeMain
    3. 14.3 练习题
      1. ILRuntime中进行CLR绑定的目的是什么?

14.更多跨域调用-CLR重定向和CLR绑定


14.1 知识点

复制之前的主入口调用

ILRuntimeManager.Instance.StartILRuntime(() =>
{
    //执行ILRuntime当中的一个静态函数
    //静态函数中 就是热更工程自己处理的逻辑了
    ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
});

复习 CLR是什么

CLR(Common Language Runtime)

公共语言运行时,它是.Net Framework的基础,所有的.Net技术都是建立在此之上的。它帮助我们实现了跨语言和跨平台。

它是一个在执行时管理代码的代码,提供内存管理、线程管理等等核心服务,就好像一个小型的操作系统一样。所以形象的把它称为“.Net虚拟机”。

如果想要应用程序在目标操作系统上能够运行,就必须依靠.Net提供的CLR环境来支持。

CLR重定向是什么

CLR重定向主要是用于对热更工程中的的一些方法进行”挟持”,就是将原本的方法”挟持”了,重新实现方法里面的逻辑,达到重定向的目的。

说人话:

我们可以通过CLR重定向,将某一个方法的执行定位到我们的自定义逻辑中,而不是执行原本的方法逻辑。有点类似重写。

CLR绑定的作用和原理

默认情况下,ILRuntime热更工程调用Unity主工程相关内容都会通过反射来调用,这样有2个缺点:

  1. 性能较低,反射调用比直接调用效率低。
  2. IL2CPP打包时容易被裁剪。

因此ILRuntime提供了自动分析生成CLR绑定的工具,它的作用是:

  1. 可以提高性能,将反射调用变为了直接调用。
  2. 避免IL2CPP裁剪有用内容。

原理:

CLR绑定,就是借助了ILRuntime的CLR重定向机制来实现的,本质上就是将方法的反射调用重定向到了我们自定义的方法里面来。

注意:

每次我们打包发布工程之前都要记得生成CLR绑定。

如何进行CLR绑定

CLR绑定步骤概述

  1. 打开 Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCLRBinding.cs 代码,在 InitILRuntime 函数中注册跨域继承相关的类以及其他内容(以后会讲解)。
  2. 点击 工具栏——>ILRuntime——>通过自动分析热更DLL生成CLR绑定,此时就可以在 ILRuntimeCLRBinding.cs 代码中设置的导出路径中看到生成的绑定代码。
  3. 在初始化处添加:ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain)

注意:

如果在CLR绑定注册前进行了CLR重定向相关设置,为了保证自定义的重定向能够正常使用,初始化CLR绑定一定要放在最后一步,这样就不会影响自己想要保留的重定向等初始化操作了。

打开ILRuntimeCLRBinding脚本,在InitILRuntime函数添加我们注册适配器的代码。添加了之后等一下点击才能生成我们跨域继承适配器的绑定类。

static void InitILRuntime(ILRuntime.Runtime.Enviorment.AppDomain domain)
{
    //这里需要注册所有热更DLL中用到的跨域继承Adapter,否则无法正确抓取引用
    domain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter());
    domain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
    domain.RegisterCrossBindingAdaptor(new TestClassBaseAdapter());
    
    
    //注册跨域继承的父类适配器 我们自己手动添加进来的 其他注册是其他官方测试示例添加的
    domain.RegisterCrossBindingAdaptor((new Lesson12_FatherAdapter()));
    domain.RegisterCrossBindingAdaptor((new Lesson13_FatherAdapter()));
    domain.RegisterCrossBindingAdaptor((new Lesson13_InterfaceAdapter()));
    domain.RegisterCrossBindingAdaptor((new Lesson13_InheritAllAdapter()));
    
    
    domain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
    domain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());
    domain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
}

点击 工具栏——>ILRuntime——>通过自动分析热更DLL生成CLR绑定,执行的其实就是ILRuntimeCLRBinding脚本的GenerateCLRBindingByAnalysis方法。此时就可以在ILRuntimeCLRBinding.cs 代码中设置的导出路径中看到生成的绑定代码。我们自己写的适配器代码绑定也被生成了。


在ILRuntimeManager的InitILRuntime方法中添加注册CLR绑定信息的逻辑

//注册CLR绑定信息
ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);

CLR绑定性能提升测试

测试思路

我们再ILRuntime中去调用Unity中的方法。如果不进行CLR绑定,就是通过反射;如果绑定了,就是直接调用。我们可以通过测试函数来体现出性能的提升。

在Unity工程中写一个静态测试函数

/// <summary>
/// 用于测试CLR绑定性能提升的函数
/// </summary>
/// <param name="i"></param>
/// <param name="j"></param>
/// <returns></returns>
public static int TestFun(int i, int j)
{
    return i + j;
}

在ILRuntime工程进行十万次调用测试,重新生成代码

//得到当前系统时间
System.DateTime currentTime = System.DateTime.Now;
for (int i = 0; i < 100000; i++)
{
    Lesson14_更多跨域调用_CLR重定向和CLR绑定.TestFun(i, i);
}
Debug.Log("十万次循环花费的时间:" + (System.DateTime.Now - currentTime).TotalMilliseconds + "ms");

运行Unity,测试十万次循环的事件。注意,现在直接在Unity运行是不会优化性能的。因为我们是再之前绑定后,新加的代码。之前绑定时还没有TestFun这个方法,在IlRuntime工程也没有调用。

重新点击CLR绑定。会分析有哪些使用的方法和类。可以看到因为我们用了Lesson14脚本所以也被绑定了,内部绑定的是TestFun方法。因为在IlRuntime工程调用了。



重新运行优化性能后的代码,性能得到显著提升。

自定义CLR重定向

自定义CLR重定向概述

我们以Debug.Log举例,如果再ILRuntime工程中调用Debug.Log,我们是无法获取到热更工程中的脚本、行号相关信息的。比如之前我们 Debug.Log(“十万次循环花费的时间:” + (System.DateTime.Now - currentTime).TotalMilliseconds + “ms”); 在Unity看不到热更工程是在哪一行打印的。我们可以通过CLR重定向的形式获取到信息后再打印。

步骤:

  1. 利用CLR绑定中自动生成的绑定代码,反射获取Debug中的Log函数,对其进行重定向。
  2. 在重定向中调用Debug.Log之前,获取DLL内的堆栈信息最后拼接打印。

注意:

要使用CLR重定向时,需要在unsafe语句块中使用,所以我们需要在定义重定向函数和使用重定向函数的地方加上unsafe。

主要目标是,让Unity的Log走进我们自己写的Log函数中,在我们的Log中能打印在热更工程行号信息。现在我们直接运行是看不到的行号的。

打开CLR绑定UnityDebug相关脚本,可以看到CLR绑定是通过反射得到方法,并替换成下方的自己Log0方法


static StackObject* Log_0(ILIntepreter __intp, StackObject* __esp, AutoList __mStack, CLRMethod __method, bool isNewObj)
{
    ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
    StackObject* ptr_of_this_method;
    StackObject* __ret = ILIntepreter.Minus(__esp, 1);

    ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
    System.Object @message = (System.Object)typeof(System.Object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (CLR.Utils.Extensions.TypeFlags)0);
    __intp.Free(ptr_of_this_method);


    UnityEngine.Debug.Log(@message);

    return __ret;
}

我们可以拷贝Log_0方法到我们Ilruntime管理器,再原绑定函数的基础上修改,改方法名改成MyLog,改成非静态的方法。AutoList改成List。注意因为用到指针相关,要用到unsafe关键字。原Debug绑定脚本没使用unsafe修饰是因为他一整个类都是unsafe修饰的,unsafe class UnityEngine_Debug_Binding。使用__domain.DebugService.GetStackTrace(__intp);获取行号,打印时拼接了行号信息。
// MyLog函数用于记录日志,并输出调用栈信息
// 参数:
//   __intp: ILRuntime解释器实例
//   __esp: 栈指针
//   __mStack: 托管堆栈
//   __method: 当前方法
//   isNewObj: 是否为新对象
unsafe StackObject* MyLog(ILIntepreter __intp, StackObject* __esp, List<object> __mStack, CLRMethod __method, bool isNewObj)
{
    // 获取当前应用程序域
    ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
    // 指针的声明
    StackObject* ptr_of_this_method;
    // 为了方便减法运算,计算栈顶指针位置
    StackObject* __ret = ILIntepreter.Minus(__esp, 1);

    // 获取栈顶参数的指针
    ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
    // 将栈顶参数转换为System.Object类型
    System.Object @message = (System.Object)typeof(System.Object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (ILRuntime.CLR.Utils.Extensions.TypeFlags)0);
    // 释放栈顶参数的指针
    __intp.Free(ptr_of_this_method);

    // 获取调用栈信息 是我们自己添加的
    var stackTrace = __domain.DebugService.GetStackTrace(__intp);

    // 输出日志信息和调用栈信息
    UnityEngine.Debug.Log(@message + "\n" + stackTrace);

    return __ret;
}

查看自动生成的绑定脚本UnityEngine_Debug_Binding的重定向代码逻辑,准备进行模仿。在Ilruntime管理器的InitILRuntime方法中,要在注册CLR绑定信息前,进行CLR重定向MyLog中。使用反射得到Debug的Type,再得到Debug中Log方法的方法信息进行重定向。模仿UnityEngine_Debug_Binding的重定向代码逻辑即可。

Type type = typeof(UnityEngine.Debug);
args = new Type[]{typeof(System.Object)};
method = type.GetMethod("Log", flag, null, args, null);
app.RegisterCLRMethodRedirection(method, Log_0);
/// <summary>
/// 初始化ILRuntime相关的内容
/// </summary>
unsafe private void InitILRuntime()
{
    //其他初始化
            
    //委托注册
    //...
            
    //注册跨域继承的父类适配器
     //...          
    


    //CLR重定向内容 要写到CLR绑定之前 我们重定向到自己的MyLog函数中
    //得到想要重定向类的Type
    System.Type debugType = typeof(Debug);
    //得到想要重定向方法的方法信息
    MethodInfo methodInfo = debugType.GetMethod("Log", new System.Type[] { typeof(object) });
    //进行CLR重定向
    appDomain.RegisterCLRMethodRedirection(methodInfo, MyLog);
    




    //注册CLR绑定信息
    ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
    
    //初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
    appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
}

现在可以看到我们在热更工程第几行打印信息的。


总结

CLR绑定就是利用CLR重定向将原本需要反射调用的内容变为直接调用,可以帮助我们:

  1. 提升ILRuntime的性能。
  2. 避免IL2CPP打包时裁剪我们需要用的内容。

14.2 知识点代码

ILRuntimeCLRBinding

#if UNITY_EDITOR
using UnityEditor;
using UnityEngine;
using System;
using System.Text;
using System.Collections.Generic;
using ILRuntimeDemo;
using Lesson12_FatherNameSpace;
using Lesson13_FatherNameSpace;
using Lesson13_InheritAllNameSpace;
using Lesson13_InterfaceNameSpace;

[System.Reflection.Obfuscation(Exclude = true)]
public class ILRuntimeCLRBinding
{
    [MenuItem("ILRuntime/通过自动分析热更DLL生成CLR绑定")]
    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/Samples/ILRuntime/Generated");
        }

        AssetDatabase.Refresh();
    }

    [MenuItem("ILRuntime/删除所有CLR绑定")]
    static void DeleteCLRBindins()
    {
        System.IO.Directory.Delete("Assets/Samples/ILRuntime/Generated", true);
        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.RegisterCrossBindingAdaptor((new Lesson12_FatherAdapter()));
        domain.RegisterCrossBindingAdaptor((new Lesson13_FatherAdapter()));
        domain.RegisterCrossBindingAdaptor((new Lesson13_InterfaceAdapter()));
        domain.RegisterCrossBindingAdaptor((new Lesson13_InheritAllAdapter()));
        
        
        domain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
        domain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());
        domain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
    }
}
#endif

Lesson14_更多跨域调用_CLR重定向和CLR绑定

using System.Collections;
using System.Collections.Generic;
using BaseFramework;
using UnityEngine;

public class Lesson14_更多跨域调用_CLR重定向和CLR绑定 : MonoBehaviour
{
    void Start()
    {
        #region 复制之前的主入口调用

        ILRuntimeManager.Instance.StartILRuntime(() =>
        {
            //执行ILRuntime当中的一个静态函数
            //静态函数中 就是热更工程自己处理的逻辑了
            ILRuntimeManager.Instance.appDomain.Invoke("HotFix_Project.ILRuntimeMain", "Start", null, null);
        });

        #endregion

        #region 复习 CLR是什么(Unity进阶之C#知识补充)

        //CLR(Common Language Runtime)
        //公共语言运行时,它是.Net Framework的基础,
        //所有的.Net技术都是建立在此之上的
        //它帮助我们实现了跨语言和跨平台

        //它是一个在执行时管理代码的代码,提供内存管理、
        //线程管理等等核心服务,就好像一个小型的操作系统一样
        //所以形象的把它称为“.Net虚拟机”。
        //如果想要应用程序在目标操作系统上能够运行
        //就必须依靠.Net提供的CLR环境来支持

        #endregion

        #region 知识点一 CLR重定向是什么

        //CLR重定向主要是用于对热更工程中的的一些方法进行"挟持"
        //就是将原本的方法"挟持"了,重新实现方法里面的逻辑
        //达到 重定向的目的

        //说人话:
        //我们可以通过CLR重定向,将某一个方法的执行定位到我们的自定义逻辑中,而不是执行原本的方法逻辑
        //有点类似重写

        #endregion

        #region 知识点二 CLR绑定的作用和原理

        //默认情况下,ILRuntime热更工程调用Unity主工程相关内容都会通过反射来调用
        //这样有2个缺点:
        //1.性能较低,反射调用比直接调用效率低
        //2.IL2CPP打包时容易被裁剪

        //因此ILRuntime提供了自动分析生成CLR绑定的工具
        //它的作用是:
        //1.可以提高性能,将反射调用变为了直接调用
        //2.避免IL2CPP裁剪有用内容

        //原理:
        //CLR绑定,就是借助了ILRuntime的CLR重定向机制来实现的
        //本质上就是将方法的反射调用重定向到了我们自定义的方法里面来

        //注意:
        //每次我们打包发布工程之前都要记得生成CLR绑定

        #endregion

        #region 知识点三 如何进行CLR绑定

        //1. 打开 Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCLRBinding.cs 代码
        //   在 InitILRuntime 函数中注册跨域继承相关的类 以及 其他内容(以后会讲解)
        //2. 点击 工具栏——>ILRuntime——>通过自动分析热更DLL生成CLR绑定
        //   此时就可以在ILRuntimeCLRBinding.cs 代码中设置的到处路径中看到生成的绑定代码
        //3. 在初始化处 添加:ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);

        //注意:
        //如果在CLR绑定注册前进行了CLR重定向相关设置
        //为了保证自定义的重定向能够正常使用
        //初始化CLR绑定一定要放在最后一步
        //这样就不会影响自己想要保留的重定向等初始化操作了

        #endregion

        #region 知识点四 CLR绑定性能提升测试

        //我们再ILRuntime中去调用Unity中的方法
        //如果不进行CLR绑定,就是通过反射
        //如果绑定了,就是直接调用
        //我们可以通过测试函数来体现出性能的提升

        #endregion

        #region 知识点五 自定义CLR重定向

        //我们以Debug.Log举例
        //如果再ILRuntime工程中调用Debug.Log
        //我们是无法获取到热更工程中的脚本、行号相关信息的
        //比如之前我们 Debug.Log("十万次循环花费的时间:" + (System.DateTime.Now - currentTime).TotalMilliseconds + "ms"); 
        //在Unity看不到热更工程是在哪一行打印的
        //我们可以通过CLR重定向的形式获取到信息后再打印

        //步骤:
        //1.利用CLR绑定中自动生成的绑定代码
        //  反射获取Debug中的Log函数
        //  对其进行重定向
        //2.在重定向中调用Debug.Log之前
        //  获取DLL内的堆栈信息 最后拼接打印

        //注意:
        //要使用CLR重定向时,需要在unsafe语句块中使用
        //所以我们需要在定义重定向函数和使用重定向函数的地方加上unsafe

        #endregion

        #region 总结

        //CLR绑定就是利用CLR重定向将原本需要反射调用的内容变为直接调用
        //可以帮助我们
        //1.提升ILRuntime的性能
        //2.避免IL2CPP打包时裁剪我们需要用的内容

        #endregion
    }

    /// <summary>
    /// 用于测试CLR绑定 性能提升的函数
    /// </summary>
    /// <param name="i"></param>
    /// <param name="j"></param>
    /// <returns></returns>
    public static int TestFun(int i, int j)
    {
        return i + j;
    }
}

UnityEngine_Debug_Binding

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;

using ILRuntime.CLR.TypeSystem;
using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Runtime.Stack;
using ILRuntime.Reflection;
using ILRuntime.CLR.Utils;
#if DEBUG && !DISABLE_ILRUNTIME_DEBUG
using AutoList = System.Collections.Generic.List<object>;
#else
using AutoList = ILRuntime.Other.UncheckedList<object>;
#endif
namespace ILRuntime.Runtime.Generated
{
    unsafe class UnityEngine_Debug_Binding
    {
        public static void Register(ILRuntime.Runtime.Enviorment.AppDomain app)
        {
            BindingFlags flag = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
            MethodBase method;
            Type[] args;
            Type type = typeof(UnityEngine.Debug);
            args = new Type[]{typeof(System.Object)};
            method = type.GetMethod("Log", flag, null, args, null);
            app.RegisterCLRMethodRedirection(method, Log_0);
            args = new Type[]{typeof(System.String), typeof(System.Object[])};
            method = type.GetMethod("LogFormat", flag, null, args, null);
            app.RegisterCLRMethodRedirection(method, LogFormat_1);


        }


        static StackObject* Log_0(ILIntepreter __intp, StackObject* __esp, AutoList __mStack, CLRMethod __method, bool isNewObj)
        {
            ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
            StackObject* ptr_of_this_method;
            StackObject* __ret = ILIntepreter.Minus(__esp, 1);

            ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
            System.Object @message = (System.Object)typeof(System.Object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (CLR.Utils.Extensions.TypeFlags)0);
            __intp.Free(ptr_of_this_method);


            UnityEngine.Debug.Log(@message);

            return __ret;
        }

        static StackObject* LogFormat_1(ILIntepreter __intp, StackObject* __esp, AutoList __mStack, CLRMethod __method, bool isNewObj)
        {
            ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
            StackObject* ptr_of_this_method;
            StackObject* __ret = ILIntepreter.Minus(__esp, 2);

            ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
            System.Object[] @args = (System.Object[])typeof(System.Object[]).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (CLR.Utils.Extensions.TypeFlags)0);
            __intp.Free(ptr_of_this_method);

            ptr_of_this_method = ILIntepreter.Minus(__esp, 2);
            System.String @format = (System.String)typeof(System.String).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (CLR.Utils.Extensions.TypeFlags)0);
            __intp.Free(ptr_of_this_method);


            UnityEngine.Debug.LogFormat(@format, @args);

            return __ret;
        }



    }
}

ILRuntimeManager

using ILRuntime.Mono.Cecil.Pdb;
using ILRuntime.Runtime.Enviorment;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Threading;
using ILRuntime.CLR.Method;
using ILRuntime.CLR.Utils;
using ILRuntime.Runtime.Intepreter;
using ILRuntime.Runtime.Stack;
using Lesson12_FatherNameSpace;
using Lesson13_FatherNameSpace;
using Lesson13_InheritAllNameSpace;
using Lesson13_InterfaceNameSpace;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;

namespace BaseFramework
{
    public class ILRuntimeManager : BaseSingletonInMonoBehaviour<ILRuntimeManager>
    {
        public AppDomain appDomain;

        //用于存储加载出来的 两个文件的内存流对象
        private MemoryStream dllStream;
        private MemoryStream pdbStream;

        private bool isStart = false;


        /// <summary>
        /// 用于启动ILRuntime初始化方法
        /// </summary>
        public void StartILRuntime(UnityAction callBack = null)
        {
            if (!isStart)
            {
                isStart = true;
                appDomain = new AppDomain();
                StartCoroutine(LoadHotUpdateInfo(callBack));
            }
        }

        public void StopILRuntime()
        {
            //1.释放流
            if (dllStream != null)
                dllStream.Close();
            if (pdbStream != null)
                pdbStream.Close();
            dllStream = null;
            pdbStream = null;
            //2.清空appDomain
            appDomain = null;

            isStart = false;
        }

        /// <summary>
        /// 初始化ILRuntime相关的内容
        /// </summary>
        unsafe private void InitILRuntime()
        {
            //其他初始化
            
            //无参无返回值不需要委托注册
            //MyUnityDel1因为转成的是无参无返回值的Action,Action在热更新里可以直接使用,所以不需要进行委托的注册
            //委托转换器注册(要把自定义委托无参无返回值MyUnityDel1委托转换为Action,其他自定义委托可以看语法也转成Action或Func)
            appDomain.DelegateManager.RegisterDelegateConvertor<MyUnityDel1>((act) =>
            {
                return new MyUnityDel1(() =>
                {
                    ((System.Action)act)();
                });
            });

            //有参有返回值委托注册 因为是自己定的规则要自己手动进行委托注册
            appDomain.DelegateManager.RegisterFunctionDelegate<System.Int32, System.Int32, System.Int32>();
            
            //委托转换器注册(要把自定义委托有参有返回值MyUnityDel2委托转换为Func,其他自定义委托可以看语法也转成Action或Func)
            appDomain.DelegateManager.RegisterDelegateConvertor<MyUnityDel2>((act) =>
            {
                return new MyUnityDel2((i, j) =>
                {
                    return ((System.Func<System.Int32, System.Int32, System.Int32>)act)(i, j);
                });
            });

            
            //注册跨域继承的父类适配器
            appDomain.RegisterCrossBindingAdaptor((new Lesson12_FatherAdapter()));
            
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_FatherAdapter()));
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_InterfaceAdapter()));
            
            appDomain.RegisterCrossBindingAdaptor((new Lesson13_InheritAllAdapter()));
            
            
            
            
            
            
            
            //CLR重定向内容 要写到CLR绑定之前
            //得到想要重定向类的Type
            System.Type debugType = typeof(Debug);
            //得到想要重定向方法的方法信息
            MethodInfo methodInfo = debugType.GetMethod("Log", new System.Type[] { typeof(object) });
            //进行CLR重定向
            appDomain.RegisterCLRMethodRedirection(methodInfo, MyLog);
            
            
            
            //注册CLR绑定信息
            ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
            
            
            
            //初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
            appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
        }

        
        
        
        // MyLog函数用于记录日志,并输出调用栈信息
        // 参数:
        //   __intp: ILRuntime解释器实例
        //   __esp: 栈指针
        //   __mStack: 托管堆栈
        //   __method: 当前方法
        //   isNewObj: 是否为新对象
        unsafe StackObject* MyLog(ILIntepreter __intp, StackObject* __esp, List<object> __mStack, CLRMethod __method, bool isNewObj)
        {
            // 获取当前应用程序域
            ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
            // 指针的声明
            StackObject* ptr_of_this_method;
            // 为了方便减法运算,计算栈顶指针位置
            StackObject* __ret = ILIntepreter.Minus(__esp, 1);

            // 获取栈顶参数的指针
            ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
            // 将栈顶参数转换为System.Object类型
            System.Object @message = (System.Object)typeof(System.Object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (ILRuntime.CLR.Utils.Extensions.TypeFlags)0);
            // 释放栈顶参数的指针
            __intp.Free(ptr_of_this_method);

            // 获取调用栈信息 是我们自己添加的
            var stackTrace = __domain.DebugService.GetStackTrace(__intp);

            // 输出日志信息和调用栈信息
            UnityEngine.Debug.Log(@message + "\n" + stackTrace);

            return __ret;
        }

        
        
        
        
        
        
        
        
        /// <summary>
        /// 启动完毕并且初始化完毕后 想要执行的热更新的逻辑
        /// </summary>
        private void ILRuntimeLoadOverDoSomthing()
        {
        }

        /// <summary>
        /// 去异步加载我们的热更相关的dll和pdb文件
        /// </summary>
        /// <returns></returns>
        IEnumerator LoadHotUpdateInfo(UnityAction callBack)
        {
            //这部分知识点在 Unity网络开发基础当中有讲解
            //加载本地的DLL文件
#if UNITY_ANDROID
        UnityWebRequest reqDll = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.dll");
#else
            UnityWebRequest reqDll =
                UnityWebRequest.Get("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll");
#endif
            yield return reqDll.SendWebRequest();
            //如果失败了
            if (reqDll.result != UnityWebRequest.Result.Success)
                print("加载DLL文件失败" + reqDll.responseCode + reqDll.result);
            //读取加载的DLL数据
            byte[] dll = reqDll.downloadHandler.data;
            reqDll.Dispose();

#if UNITY_ANDROID
        UnityWebRequest reqPdb = UnityWebRequest.Get(Application.streamingAssetsPath + "/HotFix_Project.pdb");
#else
            UnityWebRequest reqPdb =
                UnityWebRequest.Get("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb");
#endif
            yield return reqPdb.SendWebRequest();
            //如果失败了
            if (reqPdb.result != UnityWebRequest.Result.Success)
                print("加载Pdb文件失败" + reqPdb.responseCode + reqPdb.result);
            //读取加载的DLL数据
            byte[] pdb = reqPdb.downloadHandler.data;
            reqPdb.Dispose();

            //3.将加载的数据以流的形式(文件流或者内存流对象)传递给AppDomain对象中的LoadAssembly方法
            //注意 这里使用流 不要用完就关 一定要等到热更相关内容不使用了 再关闭
            dllStream = new MemoryStream(dll);
            pdbStream = new MemoryStream(pdb);
            //将我们两个文件的内存流用于初始化 appDomain 我们之后就可以通过该对象来执行我们对应的热更代码了
            appDomain.LoadAssembly(dllStream, pdbStream, new PdbReaderProvider());

            InitILRuntime();

            ILRuntimeLoadOverDoSomthing();

            //当ILRuntime启动完毕 想要在外部执行的内容
            callBack?.Invoke();
        }
    }
}

ILRuntimeMain

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnityEngine;

namespace HotFix_Project
{

    #region 委托调用

    public delegate void MyILRuntimeDel1();
    public delegate int MyILRuntimeDel2(int i, int j);

    #endregion


    class ILRuntimeMain
    {
        /// <summary>
        /// 把逻辑处理权交给热更工程
        /// 它是一个启动函数
        /// </summary>
        public static void Start()
        {
            #region ILRuntime调用Unity

            //使用ILRuntime创建的空物体
            GameObject obj = new GameObject("ILRuntime创建的空物体");
            obj.transform.position = new Vector3(10, 10, 10);
            //Debug.Log(obj.transform.position);//(10.00, 10.00, 10.00)

            //添加物理的dll后 可以正常使用Rigidbody了
            Rigidbody rigidBody = obj.AddComponent<Rigidbody>();
            rigidBody.mass = 9999;

            // 可以直接添加Unity自己写的脚本
            obj.AddComponent<Lesson10_Test>();

            #endregion



            #region 委托调用



            ////在ILRuntime中 申明 Unity中自定义委托变量 装载 ILRuntime的函数 正常使用
            //MyUnityDel1 fun = Fun1;
            //fun();//IL_Fun1

            //MyUnityDel2 fun2 = Fun2;
            //int result = fun2(5, 6);//IL_Fun2
            //Debug.Log(result);//11




            ////在Unity中 申明 Unity中自定义委托变量 关联ILRuntime工程中的函数

            ////找到我们在Unity的脚本
            //Lesson11_更多跨域调用_委托调用 lesson11 = GameObject.FindObjectOfType<Lesson11_更多跨域调用_委托调用>();

            ////直接设置Unity中自定义委托变量为ILRuntime工程中的函数会报错
            ////报错信息:KeyNotFoundException: Cannot find convertor for global::MyUnityDel1

            ////使用自定义委托
            ////注册MyUnityDel1和MyUnityDel2的委托和委托转换器后才能正常执行
            //lesson11.fun = Fun1;
            //lesson11.fun();//IL_Fun1
            //lesson11.fun2 = Fun2;
            //result = lesson11.fun2(7, 7);//IL_Fun2
            //Debug.Log(result);//14




            ////使用Action或者Func
            ////因为使用的是Action或者Func 不需要注册委托转换器
            //lesson11.funAction = Fun1;
            //lesson11.funAction();//IL_Fun1
            //lesson11.funFunc = Fun2;
            //result = lesson11.funFunc(8, 8);//IL_Fun2
            //Debug.Log(result);//16



            ////在ILRuntime中自定义委托后使用 直接使用
            //MyILRuntimeDel1 funIL = Fun1;
            //funIL();//IL_Fun1
            //MyILRuntimeDel2 funIL2 = Fun2;
            //result = fun2(1, 1);//IL_Fun2
            //Debug.Log(result);//2

            #endregion


            #region 跨域继承Unity中的类

            //Lesson12_Son lesson12_Son = new Lesson12_Son();
            //lesson12_Son.TestFun("哈哈哈");
            //lesson12_Son.TestAbstract(99);
            //lesson12_Son.valuePublic = 100;

            #endregion

            #region 跨域继承Unity中的类的注意事项


            //Lesson13_Son lesson13_Son = new Lesson13_Son();
            //lesson13_Son.TestAbstract(666);
            //lesson13_Son.TestInterface();

            //Lesson13_InheritAllSon lesson13_InheritAll = new Lesson13_InheritAllSon();
            //lesson13_InheritAll.TestAbstract(666);
            //lesson13_InheritAll.TestInterface();

            #endregion

            #region CLR重定向和CLR绑定
            //得到当前系统时间
            System.DateTime currentTime = System.DateTime.Now;
            for (int i = 0; i < 100000; i++)
            {
                Lesson14_更多跨域调用_CLR重定向和CLR绑定.TestFun(i, i);
            }
            Debug.Log("十万次循环花费的时间:" + (System.DateTime.Now - currentTime).TotalMilliseconds + "ms");
            #endregion
        }


        #region 委托调用

        public static void Fun1()
        {
            Debug.Log("IL_Fun1");
        }

        public static int Fun2(int a, int b)
        {
            Debug.Log("IL_Fun2");
            return a + b;
        }

        #endregion

    }
}

14.3 练习题

ILRuntime中进行CLR绑定的目的是什么?

提升ILRuntime的性能
避免IL2CPP打包时裁剪我们需要用的内容



转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 785293209@qq.com

×

喜欢就点赞,疼爱就打赏