ILRuntime热更案例学习(三) ------ Reflection/CLRBinding/CLRRedirection

版权声明:个人原创,转载请注明出处 https://blog.csdn.net/dengshunhao/article/details/84959393

官方示例下载地址1: https://github.com/Ourpalm/ILRuntime

官方示例下载地址2 : https://github.com/Ourpalm/ILRuntimeU3D

官方文档地址 : https://ourpalm.github.io/ILRuntime/public/v1/guide/tutorial.html


一.Reflection

void OnHotFixLoaded()
    {
        var it = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
        Debug.Log("LoadedTypes返回的是IType类型,但是我们需要获得对应的System.Type才能继续使用反射接口");
        var type = it.ReflectionType;
        var ctor = type.GetConstructor(new System.Type[0]);
        var obj = ctor.Invoke(null);
        Debug.Log("打印一下结果");
        Debug.Log(obj);
        Debug.Log("我们试一下用反射给字段赋值");
        var fi = type.GetField("id", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
        fi.SetValue(obj, 111111);
        Debug.Log("我们用反射调用属性检查刚刚的赋值");
        var pi = type.GetProperty("ID");
        Debug.Log("ID = " + pi.GetValue(obj, null));
    }

在热更DLL中使用反射跟原生C#没有任何区别,在主工程中反射热更DLL中的类型均需要通过AppDomain取得

1.反射获取类型

热更DLL当中:

//在热更DLL中,以下两种方式均可以
Type t = typeof(TypeName);
Type t2 = Type.GetType("TypeName");

Unity主工程中:

IType type = appdomain.LoadedTypes["TypeName"];
Type t = type.ReflectedType;

 2.反射创建实例

热更DLL当中:

Type t = Type.GetType("TypeName");//或者typeof(TypeName)
//以下两种方式均可以
object instance = Activator.CreateInstance(t);
object instance = Activator.CreateInstance<TypeName>();

Unity主工程中:

object instance = appdomain.Instantiate("TypeName");

3.反射调用方法

 在热更DLL当中:

Type type = typeof(TypeName);
object instance = Activator.CreateInstance(type);
MethodInfo mi = type.GetMethod("foo");
mi.Invoke(instance, null);

Unity主工程中:

IType t = appdomain.LoadedTypes["TypeName"];
Type type = t.ReflectedType;

object instance = appdomain.Instantiate("TypeName");

//系统反射接口
MethodInfo mi = type.GetMethod("foo");
mi.Invoke(instance, null);

//ILRuntime的接口
IMethod m = t.GetMethod("foo", 0);
appdomain.Invoke(m, instance, null);

4. 反射获取和设置Field的值(字段)

都一样:

Type t;
FieldInfo fi = t.GetField("field");
object val = fi.GetValue(instance);
fi.SetValue(instance, val);

5.反射获取Attribute标注

Type t;
FieldInfo fi = t.GetField("field");
object[] attributeArr = fi.GetCustomAttributes(typeof(SomeAttribute), false);

在Unity主工程中不能通过new T()的方式来创建热更工程中的类型实例

二.CLRBinding(从热更DLL中调用Unity主工程或者Unity的接口)

ILRuntime通过CLR方法绑定机制,可以选择性的对经常使用的CLR接口进行直接调用,从而尽可能的消除反射调用开销以及额外的GC Alloc

①.找到GenerateCLRBinding方法,将你要被调用的类或者接口加入到Type中 :


public class CLRBindingTestClass
{
    public static float DoSomeTest(int a, float b)
    {
        return a + b;
    }
}

static void GenerateCLRBinding()
    {
        List<Type> types = new List<Type>();
        types.Add(typeof(int));
        types.Add(typeof(float));
        types.Add(typeof(long));
        types.Add(typeof(object));
        types.Add(typeof(string));
        types.Add(typeof(Array));
        types.Add(typeof(Vector2));
        types.Add(typeof(Vector3));
        types.Add(typeof(Quaternion));
        types.Add(typeof(GameObject));
        types.Add(typeof(UnityEngine.Object));
        types.Add(typeof(Transform));
        types.Add(typeof(RectTransform));
        types.Add(typeof(CLRBindingTestClass));  //dll要访问的类
        types.Add(typeof(Time));
        types.Add(typeof(Debug));
        types.Add(typeof(Dictionary<string, int>));
        //所有DLL内的类型的真实C#类型都是ILTypeInstance
        types.Add(typeof(List<ILRuntime.Runtime.Intepreter.ILTypeInstance>));

        ILRuntime.Runtime.CLRBinding.BindingCodeGenerator.GenerateBindingCode(types, "Assets/ILRuntime/Generated");
    }

 ②.重新生成CLR绑定代码

生成完成可以看到 :

 ③.测试 

ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain);
var type = appdomain.LoadedTypes["HotFix_Project.TestCLRBinding"];
var m = type.GetMethod("RunTest", 0);

dll中的RunTest方法:
  public static void RunTest()
        {
            for (int i = 0; i < 100000; i++)
            {
                CLRBindingTestClass.DoSomeTest(i, i);
            }
        }

 除此之外在ValueTypeBinding案例中演示了另一种方法实现绑定 : 手动编写CLR重定向方法,不过特别麻烦不推荐使用,这里不描述

三.CLRRedirection(重定向)

CLR : 通用语言运行平台(Common Language Runtime)

在反射中我们可以知道一些依赖反射的接口是没有办法直接运行的,最典型的就是在Unity主工程中通过new T()创建热更DLL内类型的实例。ILRuntime为了解决这类问题,引入了CLR重定向机制。 原理就是当IL解译器发现需要调用某个指定CLR方法时,将实际调用重定向到另外一个方法进行挟持,再在这个方法中对ILRuntime的反射的用法进行处理

1.Activator.CreateInstance的CLR重定向(不带参数)

 unsafe void OnHotFixLoaded()
{
   foreach (var i in typeof(System.Activator).GetMethods())
   {
       //找到名字为CreateInstance,并且是泛型方法的方法定义
       if (i.Name == "CreateInstance" && i.IsGenericMethodDefinition)
       {
           appdomain.RegisterCLRMethodRedirection(i, CreateInstance);
       }
   }
}

public static StackObject* CreateInstance(ILIntepreter intp, StackObject* esp, List<object> mStack, CLRMethod method, bool isNewObj)
{
    //获取泛型参数<T>的实际类型
    IType[] genericArguments = method.GenericArguments;
    if (genericArguments != null && genericArguments.Length == 1)
    {
        var t = genericArguments[0];
        if (t is ILType)//如果T是热更DLL里的类型
        {
            //通过ILRuntime的接口来创建实例
            return ILIntepreter.PushObject(esp, mStack, ((ILType)t).Instantiate());
        }
        else
            return ILIntepreter.PushObject(esp, mStack, Activator.CreateInstance(t.TypeForCLR));//通过系统反射接口创建实例
    }
    else
        throw new EntryPointNotFoundException();
}

2.Unity的Debug.Log接口重定向(带参数)

 unsafe void OnHotFixLoaded()
    {
        Debug.Log("下面介绍一个CLR重定向的典型用法,比如我们在DLL里调用Debug.Log,默认情况下是无法显示DLL内堆栈的,像下面这样");
        
        Debug.Log("但是经过CLR重定向之后可以做到输出DLL内堆栈,接下来进行CLR重定向注册");

        var mi = typeof(Debug).GetMethod("Log", new System.Type[] { typeof(object) });
        appdomain.RegisterCLRMethodRedirection(mi, Log_11);
        //这个只是为了演示加的,平时不要这么用,直接在InitializeILRuntime方法里面写CLR重定向注册就行了
        Debug.Log("我们再来调用一次刚刚的方法,注意看下一行日志的变化");
        appdomain.Invoke("HotFix_Project.TestCLRRedirection", "RunTest", null, null);
    }

    //编写重定向方法对于刚接触ILRuntime的朋友可能比较困难,比较简单的方式是通过CLR绑定生成绑定代码,然后在这个基础上改,比如下面这个代码是从UnityEngine_Debug_Binding里面复制来改的
    //如何使用CLR绑定请看相关教程和文档
    unsafe static StackObject* Log_11(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj)
    {
        //ILRuntime的调用约定为被调用者清理堆栈,因此执行这个函数后需要将参数从堆栈清理干净,并把返回值放在栈顶,具体请看ILRuntime实现原理文档
        ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
        StackObject* ptr_of_this_method;
        //这个是最后方法返回后esp栈指针的值,应该返回清理完参数并指向返回值,这里是只需要返回清理完参数的值即可
        StackObject* __ret = ILIntepreter.Minus(__esp, 1);
        //取Log方法的参数,如果有两个参数的话,第一个参数是esp - 2,第二个参数是esp -1, 因为Mono的bug,直接-2值会错误,所以要调用ILIntepreter.Minus
        ptr_of_this_method = ILIntepreter.Minus(__esp, 1);

        //这里是将栈指针上的值转换成object,如果是基础类型可直接通过ptr->Value和ptr->ValueLow访问到值,具体请看ILRuntime实现原理文档
        object message = typeof(object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack));
        //所有非基础类型都得调用Free来释放托管堆栈
        __intp.Free(ptr_of_this_method);

        //在真实调用Debug.Log前,我们先获取DLL内的堆栈
        var stacktrace = __domain.DebugService.GetStackTrance(__intp);

        //我们在输出信息后面加上DLL堆栈
        UnityEngine.Debug.Log(message + "\n" + stacktrace);

        return __ret;
    }

猜你喜欢

转载自blog.csdn.net/dengshunhao/article/details/84959393