UH4L9——CLR重定向和CLR绑定
UH4L9——CLR重定向和CLR绑定
CLR是什么
关于CLR是什么,详见:CLR —— 让应用程序在操作系统上运行
CLR(Common Language Runtime)公共语言运行时,它是.Net Framework的基础,
所有的.Net技术都是建立在此之上的,它帮助我们实现了跨语言和跨平台它是一个在执行时管理代码的代码,提供内存管理、线程管理等等核心服务,
就好像一个小型的操作系统一样,所以形象的把它称为“.Net虚拟机”。
如果想要应用程序在目标操作系统上能够运行,就必须依靠.Net提供的CLR环境来支持
本章代码关键字
1 | appDomain.RegisterCLRMethodRedirection() //对反射出来的方法消息进行重定向,重定向到另外一个方法上,使得反射调用变为直接调用 |
CLR重定向和CLR绑定
CLR重定向主要是用于对热更工程中的一些方法进行"挟持",就是将原本的方法"挟持"了,重新实现方法里面的逻辑,达到 重定向的目的
说人话:我们可以通过CLR重定向,将某一个方法的执行定位到我们的自定义逻辑中,而不是执行原本的方法逻辑,有点类似重写
CLR绑定就是利用CLR重定向将原本需要反射调用的内容变为直接调用,可以帮助我们:
- 提升ILRuntime的性能
- 避免IL2CPP打包时裁剪我们需要用的内容
CLR绑定的作用和原理
默认情况下,ILRuntime热更工程调用Unity主工程相关内容都会通过反射来调用,这样有2个缺点:
- 性能较低,反射调用比直接调用效率低
- IL2CPP打包时容易被裁剪
因此ILRuntime提供了自动分析生成CLR绑定的工具,它的作用是:
- 可以提高性能,将反射调用变为了直接调用
- 避免IL2CPP裁剪有用内容
原理:CLR绑定,就是借助了ILRuntime的CLR重定向机制来实现的,
本质上就是将方法的反射调用重定向到了我们自定义的方法里面来
注意:每次我们打包发布工程之前都要记得生成CLR绑定
如何进行CLR绑定
-
打开 Samples\ILRuntime\2.1.0\Demo\Editor\ILRuntimeCLRBinding.cs 代码,
在InitILRuntime
函数中注册跨域继承相关的类 以及 其他内容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public class ILRuntimeCLRBinding
{
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 ILRuntimeAdapter.Lesson11_TestAdapter());
domain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
domain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());
domain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
}
} -
点击 工具栏 ——> ILRuntime ——> 通过自动分析热更DLL生成CLR绑定
此时就可以在
ILRuntimeCLRBinding.cs
代码中设置的到处路径中看到生成的绑定代码
-
在初始化处,添加:
ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
注意
如果在CLR绑定注册前进行了CLR重定向相关设置,为了保证自定义的重定向能够正常使用
初始化CLR绑定一定要放在最后一步,这样就不会影响自己想要保留的重定向等初始化操作了1
2
3
4
5
6
7
8
9
10
11//初始化ILRuntime相关的方法
private void InitILRuntime()
{
//注册委托和委托转换器...
//注册跨域继承类
appDomain.RegisterCrossBindingAdaptor(new ILRuntimeAdapter.Lesson11_TestAdapter());
//注册 CLR绑定相关信息
ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
//初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
}
CLR绑定性能提升测试
我们在ILRuntime中去调用Unity中的方法,如果不进行CLR绑定,就是通过反射
如果绑定了,就是直接调用,我们可以通过测试函数来体现出性能的提升
测试函数如下:
1 | public class Lesson13 : MonoBehaviour |
热更工程内先记录一次测试前时间,循环执行100000次该函数,然后将测试后时间减去测试前时间,得到消耗时间
1 | public class ILRuntimeMain |
此时不进行CLR绑定,不对Lesson13
做CLR绑定,直接反射运行得到的耗时如下:
然后点击 ILRuntime ——> 通过自动分析热更DLL生成CLR绑定,让ILRuntime分析热更工程内使用了哪些类,然后生成绑定,再运行,耗时如下:
可见,进行CLR绑定带来了至少十倍以上的性能提升,提升效果是极大的
自定义CLR重定向
我们以Debug.Log
举例,如果再ILRuntime工程中调用Debug.Log
我们是无法获取到热更工程中的脚本、行号相关信息的,我们可以通过CLR重定向的形式获取到信息后再打印
这时,我们可以自定义重定向的方法的逻辑,将显示热更工程中的脚本、行号相关信息的逻辑添加到重定向方法内
步骤:
-
仿照CLR绑定中自动生成的绑定代码,反射获取类中的函数,对函数进行CLR重定向,以
Debug.Log
为例先获取
Debug
类的类型消息,根据类型消息反射出Log
方法的方法消息,
然后 将Log
方法消息 和 重定向的方法 一起传入到appDomain.RegisterCLRMethodRedirection
方法内,完成重定向对于重定向的方法,同样可以先复制CLR绑定中自动生成的重定向方法(将其中的
AutoList
参数改为List<Object>
参数),之后再做修改注意:
要使用CLR重定向时,需要在unsafe
语句块中使用
所以我们需要在定义重定向函数和使用重定向函数的地方加上unsafe
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35//初始化ILRuntime相关的方法
private unsafe void InitILRuntime()
{
//注册委托和委托转换器...
//注册跨域继承类
appDomain.RegisterCrossBindingAdaptor(new ILRuntimeAdapter.Lesson11_TestAdapter());
//CLR重定向内容,必须要写到CLR绑定之前!!!
System.Type debugType = typeof(Debug);
MethodInfo methodInfo = debugType.GetMethod("Log", new System.Type[] { typeof(object) });
appDomain.RegisterCLRMethodRedirection(methodInfo, MyLog);
//注册 CLR绑定相关信息
ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
//初始化ILRuntime相关信息(目前只需要告诉ILRuntime主线程的线程ID,主要目的是能够在Unity的Profiler剖析器窗口中分析问题)
appDomain.UnityMainThreadID = Thread.CurrentThread.ManagedThreadId;
}
private 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 @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;
} -
在重定向中调用
Debug.Log
之前,获取DLL内的堆栈信息 最后拼接打印调用
__domain.DebugService.GetStackTrace(__intp)
并获取其返回值,然后拼接到重定向方法调用的Debug.Log
方法内关于重定向的原理,详见:UH4L21——重定向的书写规则
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17private 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 @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;
}
此时执行方法并输出,就可以看到调用消息: