UH4L21——重定向的书写规则
UH4L21——重定向的书写规则
本章代码关键字
1 | ILIntepreter.AppDomain //通过解释器获取appDomain |
重定向的书写规则
当我们需要自己实现重定向函数时,我们只需要参考CLR绑定中生成的文档中的内容进行书写即可
只要我们大概了解了重定向函数的规则套路,我们自然能够看懂和写出我们需要的内容
回顾自定义重定向流程
前置知识:CLR重定向
- 得到重定向类的
Type
- 得到想要重定向方法的方法信息
MethodInfo
- 使用
AppDomain
中的注册重定向方法RegisterCLRMethodRedirection
,对MethodInfo
进行重定向方法关联
回顾ILRuntime中方法指令的调用
前置知识:UH4L20——解释器
在执行代码时,ILRuntime内部自己实现了的运行栈来管理内存
方法调用前后,栈的内存分布应该是这样的
1 | 栈的内存分布: |
重定向函数的规则套路
重定向的目的,就是让热更工程内,调用主工程方法从反射调用变成直接调用
这就需要我们从ILRuntime的运行栈内获取热更工程内传入参数的值,然后传入到主工程内对应的方法
如果有返回值,我们还需要将返回值传递到ILRuntime的运行栈内
以自动生成的UnityEngine_Vector3_Binding
中的Dot_0
方法为例
1 | static StackObject* Dot_0(ILIntepreter __intp, StackObject* __esp, AutoList __mStack, CLRMethod __method, bool isNewObj) |
-
通过解析器获取
appDomain
1
2
3
4
5static StackObject* Dot_0(ILIntepreter __intp, StackObject* __esp, AutoList __mStack, CLRMethod __method, bool isNewObj)
{
ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
// ...后续重定向逻辑
} -
获取栈底指针(根据函数参数数量决定减多少,用于之后返回)
参数传入的
__esp
是指向栈顶地址的指针,因此通过ILIntepreter.Minus
来减__esp
对应的地址值来获取栈底的地址指针,有多少参数减多少
Vector3
的Dot
方法有两个参数:float Dot(Vector3 lhs, Vector3 rhs)
,因此ILIntepreter.Minus
传入的数值是2
1
2
3StackObject* ptr_of_this_method;
//一开始 我们需要获取到 栈底位置 之后用于返回
StackObject* __ret = ILIntepreter.Minus(__esp, 2); -
获取函数参数
提前声明
StackObject*
变量ptr_of_this_method
,它用于获取指向存储传入函数参数的值的指针消息如果要指向参数一地址,
__esp
地址减1即可,如果要指向参数二地址,__esp
地址减2即可1
2
3
4
5StackObject* ptr_of_this_method;
//获取参数一
ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
//获取参数二
ptr_of_this_method = ILIntepreter.Minus(__esp, 2); -
参数类型转换
我们需要将 指针指向的地址 所存储的值 转换为 对应类型的变量
这样才能将热更工程中调用方法时传入的参数值直接传入到重定向的方法内
因此我们需要对 指向参数地址的指针ptr_of_this_method
进行类型转换这一步会调用
Type.CheckCLRTypes
方法,该方法参数内还需要调用StackObject.ToObject
方法,并传入CLR.Utils.Extensions.TypeFlags
枚举固定写法如下:
1
2
3
4参数类型 变量名 = (参数类型)typeof(参数类型).CheckCLRTypes( //获取Type,执行拓展方法,返回值需要强转为对应的类型
StackObject.ToObject(指向存储参数值的地址的指针), __domain, __mStack), //将解释器的栈上的某个值转换为object对象
(CLR.Utils.Extensions.TypeFlags)枚举值对应的整数 //根据转换的类型不同,使用不同的TypeFlags枚举
)其中,
CLR.Utils.Extensions.TypeFlags
的各个枚举如下:1
2
3
4
5
6
7
8
9public enum TypeFlags
{
Default = 0, //默认
IsPrimitive = 0x1, //基本类型
IsByRef = 0x2, //引用类型
IsEnum = 0x4, //枚举类型
IsDelegate = 0x8, //委托类型
IsValueType = 0x10, //值类型
}按照上面的写法,我们即可将 指针指向的地址的存储的值 转为由 对应类型(
Vector3
)的变量存储1
2
3
4
5
6
7
8
9
10
11
12
13StackObject* ptr_of_this_method;
ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
UnityEngine.Vector3 @rhs = (UnityEngine.Vector3)typeof(UnityEngine.Vector3).CheckCLRTypes(
StackObject.ToObject(ptr_of_this_method, __domain, __mStack),
(CLR.Utils.Extensions.TypeFlags)16
);
ptr_of_this_method = ILIntepreter.Minus(__esp, 2);
UnityEngine.Vector3 @lhs = (UnityEngine.Vector3)typeof(UnityEngine.Vector3).CheckCLRTypes(
StackObject.ToObject(ptr_of_this_method, __domain, __mStack),
(CLR.Utils.Extensions.TypeFlags)16
); -
释放对应栈指针内存
在将转换出来的变量传入到对应的方法之前,我们必须要释放掉栈上存储的值的内存,
释放内存方法ILIntepreter.Free()
需要 传入指向需要释放内存的地址的指针
释放参数的栈的内存后,指向栈底的指针 就会指向 栈顶 或者说 返回值应该存储在的地址1
2
3
4
5
6
7
8
9
10
11StackObject* ptr_of_this_method;
//一开始 我们需要获取到 栈底位置 之后用于返回
StackObject* __ret = ILIntepreter.Minus(__esp, 2);
ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
UnityEngine.Vector3 @rhs = (UnityEngine.Vector3)typeof(UnityEngine.Vector3).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (CLR.Utils.Extensions.TypeFlags)16);
__intp.Free(ptr_of_this_method);
ptr_of_this_method = ILIntepreter.Minus(__esp, 2);
UnityEngine.Vector3 @lhs = (UnityEngine.Vector3)typeof(UnityEngine.Vector3).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack), (CLR.Utils.Extensions.TypeFlags)16);
__intp.Free(ptr_of_this_method); -
重定向处理
获取到存储参数值的变量后,调用要重定向的方法,传入之前转换出来的参数值,有返回值则还需要一个变量去接收
1
var result_of_this_method = UnityEngine.Vector3.Dot(@lhs, @rhs);
-
返回当前栈顶(栈指针位置)
-
无返回值
直接返回之前记录的栈底位置的指针即可
1
return __ret;
-
有返回值
-
如果是简单(基本)类型,直接设置后返回
Vector3.Dot
方法返回值的类型是float
,这就是简单类型,设置后返回即可我们需要设置
__ret
指针指向的栈空间StackObject
类对象的ValueType
(返回值类型)和Value
(返回值)
然后返回__ret + 1
,即将栈指针移动到存在返回值的地址的下一位,则就代表存在返回值,需要读取这个返回值值得一提的是,对于指针变量,想要调用其指向的对象的成员,需要使用
->
,而不是.
,例如:- 如果是存储
stackObject
对象的变量要调用ValueType
成员,就是stackObject.ValueType
- 如果是存储指向
stackObject
对象的指针变量要调用ValueType
成员,就是ptr->ValueType
(假设ptr
是指针变量)
1
2
3
4
5var result_of_this_method = UnityEngine.Vector3.Dot(@lhs, @rhs);
__ret->ObjectType = ObjectTypes.Float;
*(float*)&__ret->Value = result_of_this_method;
return __ret + 1; - 如果是存储
-
如果是复杂类型,使用
PushObject
方法诸如自己声明的类,结构体等等都是复杂类型,以
Vector3.up
属性为例,它返回的就是Vector3
类型
因此需要使用ILIntepreter.PushObject
方法,传入__ret
(自声明的原本指向栈底的指针),__mStack
(参数),重定向的方法的返回值1
2
3
4
5
6
7
8
9
10static StackObject* get_up_1(ILIntepreter __intp, StackObject* __esp, AutoList __mStack, CLRMethod __method, bool isNewObj)
{
ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
StackObject* __ret = ILIntepreter.Minus(__esp, 0);
var result_of_this_method = UnityEngine.Vector3.up;
return ILIntepreter.PushObject(__ret, __mStack, result_of_this_method);
}
-
-