UH4L21——重定向的书写规则

本章代码关键字

1
2
3
4
5
6
7
ILIntepreter.AppDomain                //通过解释器获取appDomain
ILIntepreter.Minus() //移动栈指针,获取不同位置,可用于获取栈底或者指向某个参数的地址
Type.CheckCLRTypes() //将StackObject.ToObject()转换出来的对象再为某种类型的对象(返回出来的值可直接强转成对应的类型)
StackObject.ToObject() //将某个栈空间存储的值转换为对象
CLR.Utils.Extensions.TypeFlags //要转换的类型枚举,配合Type.CheckCLRTypes()使用
ILIntepreter.Free //释放某个栈上的内存
ILIntepreter.PushObject() //将复杂类型的返回值压入到栈内

重定向的书写规则

当我们需要自己实现重定向函数时,我们只需要参考CLR绑定中生成的文档中的内容进行书写即可
只要我们大概了解了重定向函数的规则套路,我们自然能够看懂和写出我们需要的内容

回顾自定义重定向流程

前置知识:CLR重定向

  1. 得到重定向类的 Type
  2. 得到想要重定向方法的方法信息 MethodInfo
  3. 使用 AppDomain​ 中的注册重定向方法 RegisterCLRMethodRedirection​,对MethodInfo​进行重定向方法关联

回顾ILRuntime中方法指令的调用

前置知识:UH4L20——解释器

在执行代码时,ILRuntime内部自己实现了的运行栈来管理内存
方法调用前后,栈的内存分布应该是这样的

1
2
3
4
栈的内存分布:
参数1 返回值
参数2 ——方法执行——> 栈指针
栈指针

重定向函数的规则套路

重定向的目的,就是让热更工程内,调用主工程方法从反射调用变成直接调用
这就需要我们从ILRuntime的运行栈内获取热更工程内传入参数的值,然后传入到主工程内对应的方法
如果有返回值,我们还需要将返回值传递到ILRuntime的运行栈内

以自动生成的UnityEngine_Vector3_Binding​中的Dot_0​方法为例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static StackObject* Dot_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, 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);

//真正的重定向相关核心逻辑处理
var result_of_this_method = UnityEngine.Vector3.Dot(@lhs, @rhs);

__ret->ObjectType = ObjectTypes.Float;
*(float*)&__ret->Value = result_of_this_method;
return __ret + 1;
}
  1. 通过解析器获取 appDomain

    1
    2
    3
    4
    5
    static StackObject* Dot_0(ILIntepreter __intp, StackObject* __esp, AutoList __mStack, CLRMethod __method, bool isNewObj)
    {
    ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
    // ...后续重定向逻辑
    }
  2. 获取栈底指针(根据函数参数数量决定减多少,用于之后返回)

    参数传入的__esp​是指向栈顶地址的指针,因此通过ILIntepreter.Minus​来减__esp​对应的地址值来获取栈底的地址指针,有多少参数减多少

    Vector3​的Dot​方法有两个参数:float Dot(Vector3 lhs, Vector3 rhs)​,因此ILIntepreter.Minus​传入的数值是2

    1
    2
    3
    StackObject* ptr_of_this_method;
    //一开始 我们需要获取到 栈底位置 之后用于返回
    StackObject* __ret = ILIntepreter.Minus(__esp, 2);
  3. 获取函数参数

    提前声明StackObject*​变量ptr_of_this_method​,它用于获取指向存储传入函数参数的值的指针消息

    如果要指向参数一地址,__esp​地址减1即可,如果要指向参数二地址,__esp​地址减2即可

    1
    2
    3
    4
    5
    StackObject* ptr_of_this_method;
    //获取参数一
    ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
    //获取参数二
    ptr_of_this_method = ILIntepreter.Minus(__esp, 2);
  4. 参数类型转换

    我们需要将 指针指向的地址 所存储的值 转换为 对应类型的变量
    这样才能将热更工程中调用方法时传入的参数值直接传入到重定向的方法内
    因此我们需要对 指向参数地址的指针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
    9
    public 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
    13
    StackObject* 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
    );
  5. 释放对应栈指针内存

    在将转换出来的变量传入到对应的方法之前,我们必须要释放掉栈上存储的值的内存,
    释放内存方法ILIntepreter.Free()​ 需要 传入指向需要释放内存的地址的指针
    释放参数的栈的内存后,指向栈底的指针 就会指向 栈顶 或者说 返回值应该存储在的地址

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    StackObject* 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);
  6. 重定向处理

    获取到存储参数值的变量后,调用要重定向的方法,传入之前转换出来的参数值,有返回值则还需要一个变量去接收

    1
    var result_of_this_method = UnityEngine.Vector3.Dot(@lhs, @rhs);
  7. 返回当前栈顶(栈指针位置)

    • 无返回值

      直接返回之前记录的栈底位置的指针即可

      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
        5
        var 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
        10
        static 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);
        }