ILRuntime热更案例学习(二) ------ Invocation/Delegate/Inheritance/错误提醒

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

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

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

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


一.Invocation案例

 void OnHotFixLoaded()
    {
        Debug.Log("调用无参数静态方法");
        //调用无参数静态方法,appdomain.Invoke("命名空间.类名", "方法名", 对象引用, 参数列表);
        appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);
        //调用带参数的静态方法
        Debug.Log("调用带参数的静态方法");
        appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest2", null, 123);

        Debug.Log("通过IMethod调用方法");
        //预先获得IMethod,可以减低每次调用查找方法耗用的时间
        IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
        //根据方法名称和参数个数获取方法
        IMethod method = type.GetMethod("StaticFunTest", 0);
        appdomain.Invoke(method, null, null);

        Debug.Log("指定参数类型来获得IMethod");
        IType intType = appdomain.GetType(typeof(int));
        //参数类型列表
        List<IType> paramList = new List<ILRuntime.CLR.TypeSystem.IType>();
        paramList.Add(intType);
        //根据方法名称和参数类型列表获取方法
        method = type.GetMethod("StaticFunTest2", paramList, null);
        appdomain.Invoke(method, null, 456);

        Debug.Log("实例化热更里的类");
        object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 });
        //第二种方式
        object obj2 = ((ILType)type).Instantiate();

        Debug.Log("调用成员方法");
        int id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj, null);
        Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id);
        id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj2, null);
        Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id);

        Debug.Log("调用泛型方法");
        IType stringType = appdomain.GetType(typeof(string));
        IType[] genericArguments = new IType[] { stringType };
        appdomain.InvokeGenericMethod("HotFix_Project.InstanceClass", "GenericMethod", genericArguments, null, "TestString");

        Debug.Log("获取泛型方法的IMethod");
        paramList.Clear();
        paramList.Add(intType);
        genericArguments = new IType[] { intType };
        method = type.GetMethod("GenericMethod", paramList, genericArguments);
        appdomain.Invoke(method, null, 33333);
    }

写的非常详细了,一路看下来只有一个地方不明白的

 object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 });

这个是调用含参构造函数进行实例化,可以看到 InstanceClass有这样一个构造函数:

public InstanceClass(int id)
        {
            UnityEngine.Debug.Log("!!! InstanceClass::InstanceClass() id = " + id);
            this.id = id;
        }

二.Delegate案例学习

这个案例带了错误的示例,所以我把不必要的清除了


public static TestDelegateMethod TestMethodDelegate;
public static TestDelegateFunction TestFunctionDelegate;
public static System.Action<string> TestActionDelegate;


void OnHotFixLoaded()
    {
        Debug.Log("完全在热更DLL内部使用的委托,直接可用,不需要做任何处理");
        appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize", null, null);
        appdomain.Invoke("HotFix_Project.TestDelegate", "RunTest", null, null);

        Debug.Log("首先需要注册委托适配器");
        //下面这些注册代码,正式使用的时候,应该写在InitializeILRuntime中
        //TestDelegateMethod, 这个委托类型为有个参数为int的方法,注册仅需要注册不同的参数搭配即可
        appdomain.DelegateManager.RegisterMethodDelegate<int>();
        //Action<string> 的参数为一个string
        appdomain.DelegateManager.RegisterMethodDelegate<string>();

        //带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个
        appdomain.DelegateManager.RegisterFunctionDelegate<int, string>();

        Debug.Log("ILRuntime内部是用Action和Func这两个系统内置的委托类型来创建实例的,所以其他的委托类型都需要写转换器");
        Debug.Log("将Action或者Func转换成目标委托类型");

        appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>((action) =>
        {
            //转换器的目的是把Action或者Func转换成正确的类型,这里则是把Action<int>转换成TestDelegateMethod
            return new TestDelegateMethod((a) =>
            {
                //调用委托实例
                ((System.Action<int>)action)(a);
            });
        });

        //对于TestDelegateFunction同理,只是是将Func<int, string>转换成TestDelegateFunction
        appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateFunction>((action) =>
        {
            return new TestDelegateFunction((a) =>
            {
                return ((System.Func<int, string>)action)(a);
            });
        });

        //下面再举一个这个Demo中没有用到,但是UGUI经常遇到的一个委托,例如UnityAction<float>
        appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction<float>>((action) =>
        {
            return new UnityEngine.Events.UnityAction<float>((a) =>
            {
                ((System.Action<float>)action)(a);
            });
        });


        Debug.Log("现在我们再来运行一次");
        appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize2", null, null);
        appdomain.Invoke("HotFix_Project.TestDelegate", "RunTest2", null, null);
        Debug.Log("运行成功,我们可以看见,用Action或者Func当作委托类型的话,可以避免写转换器,所以项目中在不必要的情况下尽量只用Action和Func");
        TestActionDelegate("Hello From Unity Main Project");
        Debug.Log("另外应该尽量减少不必要的跨域委托调用,如果委托只在热更DLL中用,是不需要进行任何注册的");
        Debug.Log("---------");
        Debug.Log("我们再来在Unity主工程中调用一下刚刚的委托试试");
        TestMethodDelegate(789);
        var str = TestFunctionDelegate(098);
        Debug.Log("!! OnHotFixLoaded str = " + str);
    }

总得来说 : 

 1.调用完全在热更DLL内部使用的委托,直接可用,不需要做任何处理

 2.将热更DLL里面的委托实例传到Unity主工程用 : 注册委托适配器 - 转换器(如果委托是Func或者Action就不需要转换) - 调用

三.Inheritance(跨域继承)案例

Unity主工程中被继承的类 :

public abstract class TestClassBase
{
    public virtual int Value
    {
        get
        {
            return 0;
        }
    }

    public virtual void TestVirtual(string str)
    {
        Debug.Log("!! TestClassBase.TestVirtual, str = " + str);
    }

    public abstract void TestAbstract(int gg);
}

先创建该类的继承适配器 :

public class InheritanceAdapter : CrossBindingAdaptor
{
    public override Type BaseCLRType
    {
        get
        {
            return typeof(TestClassBase);//这是你想继承的那个类
        }
    }

    public override Type AdaptorType
    {
        get
        {
            return typeof(Adaptor);//这是实际的适配器类
        }
    }

    public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
    {
        return new Adaptor(appdomain, instance);//创建一个新的实例
    }

    //实际的适配器类需要继承你想继承的那个类,并且实现CrossBindingAdaptorType接口
    class Adaptor : TestClassBase, CrossBindingAdaptorType
    {
        ILTypeInstance instance;
        ILRuntime.Runtime.Enviorment.AppDomain appdomain;
        IMethod mTestAbstract;
        bool mTestAbstractGot;
        IMethod mTestVirtual;
        bool mTestVirtualGot;
        bool isTestVirtualInvoking = false;
        IMethod mGetValue;
        bool mGetValueGot;
        bool isGetValueInvoking = false;
        //缓存这个数组来避免调用时的GC Alloc
        object[] param1 = new object[1];

        public Adaptor()
        {

        }

        public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
        {
            this.appdomain = appdomain;
            this.instance = instance;
        }

        public ILTypeInstance ILInstance { get { return instance; } }

        //你需要重写所有你希望在热更脚本里面重写的方法,并且将控制权转到脚本里去
        public override void TestAbstract(int ab)
        {
            if (!mTestAbstractGot)
            {
                mTestAbstract = instance.Type.GetMethod("TestAbstract", 1);
                mTestAbstractGot = true;
            }
            if (mTestAbstract != null)
            {
                param1[0] = ab;
                appdomain.Invoke(mTestAbstract, instance, param1);//没有参数建议显式传递null为参数列表,否则会自动new object[0]导致GC Alloc
            }
        }

        public override void TestVirtual(string str)
        {
            if (!mTestVirtualGot)
            {
                mTestVirtual = instance.Type.GetMethod("TestVirtual", 1);
                mTestVirtualGot = true;
            }
            //对于虚函数而言,必须设定一个标识位来确定是否当前已经在调用中,否则如果脚本类中调用base.TestVirtual()就会造成无限循环,最终导致爆栈
            if (mTestVirtual != null && !isTestVirtualInvoking)
            {
                isTestVirtualInvoking = true;
                param1[0] = str;
                appdomain.Invoke(mTestVirtual, instance, param1);
                isTestVirtualInvoking = false;
            }
            else
                base.TestVirtual(str);
        }

        public override int Value
        {
            get
            {
                if (!mGetValueGot)
                {
                    //属性的Getter编译后会以get_XXX存在,如果不确定的话可以打开Reflector等反编译软件看一下函数名称
                    mGetValue = instance.Type.GetMethod("get_Value", 1);
                    mGetValueGot = true;
                }
                //对于虚函数而言,必须设定一个标识位来确定是否当前已经在调用中,否则如果脚本类中调用base.Value就会造成无限循环,最终导致爆栈
                if (mGetValue != null && !isGetValueInvoking)
                {
                    isGetValueInvoking = true;
                    var res = (int)appdomain.Invoke(mGetValue, instance, null);
                    isGetValueInvoking = false;
                    return res;
                }
                else
                    return base.Value;
            }
        }

        public override string ToString()
        {
            IMethod m = appdomain.ObjectType.GetMethod("ToString", 0);
            m = instance.Type.GetVirtualMethod(m);
            if (m == null || m is ILMethod)
            {
                return instance.ToString();
            }
            else
                return instance.Type.FullName;
        }
    }
}

调用 :

void OnHotFixLoaded()
    {
        Debug.Log("所以现在我们来注册适配器");
        appdomain.RegisterCrossBindingAdaptor(new InheritanceAdapter());
        Debug.Log("现在再来尝试创建一个实例");
        //TestClassBase : 继承基类  HotFix_Project.TestInheritance : 子类
        obj = appdomain.Instantiate<TestClassBase>("HotFix_Project.TestInheritance");
        Debug.Log("现在来调用成员方法");
        obj.TestAbstract(123);
        obj.TestVirtual("Hello");

        Debug.Log("现在换个方式创建实例");
        //NewObject : 创建TestInheritance类的方法
        obj = appdomain.Invoke("HotFix_Project.TestInheritance", "NewObject", null, null) as TestClassBase;
        obj.TestAbstract(456);
        obj.TestVirtual("Foobar");
    }

这里面主要的难点在于创建类的继承适配器,总结下大致规则

1.继承CrossBindingAdaptor

2.实现public abstract Type BaseCLRType { get; }

 public override Type BaseCLRType
    {
        get
        {
            return typeof(TestClassBase);//返回你要继承的基类类型
        }
    }

3. 实现public abstract object CreateCLRInstance(Enviorment.AppDomain appdomain, ILTypeInstance instance);

  public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
    {
        return new Adaptor(appdomain, instance);//创建一个新的实例
    }

 Adaptor为下面要创建的类,是实际的适配器类

4.实现public abstract Type AdaptorType { get; } 

   public override Type AdaptorType
    {
        get
        {
            return typeof(Adaptor);//这是实际的适配器类
        }
    }

5.创建实际的适配器类Adaptor需要继承你想继承的那个类,并且实现CrossBindingAdaptorType接口

①.实现CrossBindingAdaptorType接口

ILTypeInstance instance;
public ILTypeInstance ILInstance { get { return instance; } }

初始化 :

 public Adaptor()
        {

        }

        public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
        {
            this.appdomain = appdomain;
            this.instance = instance;
        }

其实我这里有点奇怪,因为注册继承适配器哪里并没有调用CreateCLRInstance,也就没有调用到Adaptor的含参构造函数,而是在ILTypeInstance中有调用,不过应该是内部的机制吧,这里不深究

②.实现基类中的属性,函数(将控制权转到脚本里去)

    要注意,对于虚函数而言,必须设定一个标识位来确定是否当前已经在调用中,否则如果脚本类中调用base.Value就会造成无限循环,最终导致爆栈,属性的Get方法也属于虚函数,写法如下 :


        IMethod mGetValue;
        bool mGetValueGot;
        bool isGetValueInvoking = false; 

public override int Value
        {
            get
            {
                if (!mGetValueGot)
                {
                    //属性的Getter编译后会以get_XXX存在,如果不确定的话可以打开Reflector等反编译软件看一下函数名称
                    mGetValue = instance.Type.GetMethod("get_Value", 1);
                    mGetValueGot = true;
                }
                //对于虚函数而言,必须设定一个标识位来确定是否当前已经在调用中,否则如果脚本类中调用base.Value就会造成无限循环,最终导致爆栈
                if (mGetValue != null && !isGetValueInvoking)
                {
                    isGetValueInvoking = true;
                    var res = (int)appdomain.Invoke(mGetValue, instance, null);
                    isGetValueInvoking = false;
                    return res;
                }
                else
                    return base.Value;
            }
        }

③.不要忘记tostring()实现

public override string ToString()
        {
            IMethod m = appdomain.ObjectType.GetMethod("ToString", 0);
            m = instance.Type.GetVirtualMethod(m);
            if (m == null || m is ILMethod)
            {
                return instance.ToString();
            }
            else
                return instance.Type.FullName;
        }

四.补充下CLR中错误提醒 

1.注册委托适配器

Cannot find Delegate Adapter for:HotFix_Project.TestDelegate.Method(Int32 a), Please add following code:
appdomain.DelegateManager.RegisterMethodDelegate<System.Int32>();

2.没有为其他的委托类型写转换器(转成Func / Action)

Cannot find convertor for TestDelegateMethod
Please add following code:
appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>((act) =>
{
    return new TestDelegateMethod((a) =>
    {
        ((Action<System.Int32>)act)(a);
    });
});

3.跨域继承未注册适配器

System.TypeLoadException: Cannot find Adaptor for:TestClassBase

猜你喜欢

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