ILRuntime断点调试

VS有官方插件可以支持,我这边说一下怎么用Rider进行调试

(待补充)

使用委托

如果一个委托在热更中定义,并且被热更中的方法调用,那么我们可以直接调用这个方法,不需要额外处理

如果在主工程中定义了委托,但想要在热更中进行操作,那么需要有适配器以及转换器

如有以下主工程代码

public delegate void TestDelegateMethod(int a);
public delegate string TestDelegateFunction(int a);

public class DelegateDemo : MonoBehaviour
{
    public static TestDelegateMethod TestMethodDelegate;
    public static TestDelegateFunction TestFunctionDelegate;
    public static System.Action<string> TestActionDelegate;
}

有以下热更代码

public class TestDelegate
{
    public static void Initialize2()
    {
        DelegateDemo.TestMethodDelegate = Method;
        DelegateDemo.TestFunctionDelegate = Function;
        DelegateDemo.TestActionDelegate = Action;
    }
    
	static void Method(int a)
	{
		UnityEngine.Debug.Log("!! TestDelegate.Method, a = " + a);
	}

	static string Function(int a)
	{
		return a.ToString();
	}	

	static void Action(string a)
	{
		UnityEngine.Debug.Log("!! TestDelegate.Action, a = " + a);
	}
}
  • 适配器

    由于主工程中定义的委托完全没有被使用,那么当使用IL2CPP编译时,可能导致代码直接被优化(删了),从而引发报错,因此我们必须象征性的提前注册以下相关委托(象征性的使用以下)

    //TestDelegateMethod, 这个委托类型为有个参数为int的方法,注册仅需要注册不同的参数搭配即可
    appdomain.DelegateManager.RegisterMethodDelegate<int>();
    //带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个
    appdomain.DelegateManager.RegisterFunctionDelegate<int, string>();
    //Action<string> 的参数为一个string
    appdomain.DelegateManager.RegisterMethodDelegate<string>();
    

    注意,并不是每一个委托对应一个适配器,而是每一个类型组合对应一个适配器

  • 转换器

    在ILRT的内部,所有的委托均采用Action和Func进行存储,这和我们自定义为的委托显然是不一样的, 因此我们必须提供一个转换器,可以把对应的委托转换成ILRT支持的类型,如果你的委托就是Action或者Func类型,那不需要转换器,原因在于当我们注册适配器时,ILRT就会自动注册转换器了

    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);
        });
    });
    

    看一下关于转换器注册的源码

    public void RegisterDelegateConvertor<T>(Func<Delegate, Delegate> action)
    {
        var type = typeof(T);
        if (type.IsSubclassOf(typeof(Delegate)))
        {
            clrDelegates[type] = action;
        }
        else
            throw new NotSupportedException();
    }
    

    参数是一个Func委托,其参数是一个委托,返回值也是一个委托,emmm,有转换器内味了是吧

    再观察我们的转换过程,最终是return了一个new的目标委托类型,而这个委托中所需要执行的方法,则来自于Func中的参数,我们先转成目标Action,再执行

image-20211114161508747

继承

当想在热更中继承主工程的类时,依然需要一个适配器

在主工程有如下类

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

        }
    }

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

    public abstract void TestAbstract(int gg);
}

则需要进行适配器的注册

appdomain.RegisterCrossBindingAdaptor(new TestClassBaseAdapter());
public class TestClassBaseAdapter : CrossBindingAdaptor
{
    // 带有返回值的方法
    static CrossBindingFunctionInfo<System.Int32>
        mget_Value_0 = new CrossBindingFunctionInfo<System.Int32>("get_Value");

    // 不带返回值的方法
    static CrossBindingMethodInfo<System.Int32> mset_Value_1 = new CrossBindingMethodInfo<System.Int32>("set_Value");

    static CrossBindingMethodInfo<System.String> mTestVirtual_2 =
        new CrossBindingMethodInfo<System.String>("TestVirtual");

    static CrossBindingMethodInfo<System.Int32> mTestAbstract_3 =
        new CrossBindingMethodInfo<System.Int32>("TestAbstract");

    public override Type BaseCLRType
    {
        get { return typeof(global::TestClassBase); }
    }

    public override Type AdaptorType
    {
        get { return typeof(Adapter); }
    }

    public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
    {
        return new Adapter(appdomain, instance);
    }

    // 真的适配器
    public class Adapter : global::TestClassBase, CrossBindingAdaptorType
    {
        ILTypeInstance instance;
        ILRuntime.Runtime.Enviorment.AppDomain appdomain;

        public Adapter()
        {
        }

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

        public ILTypeInstance ILInstance
        {
            get { return instance; }
        }

        public override void TestVirtual(System.String str)
        {
            // 如果热更中调用了base.TestVirtual();
            if (mTestVirtual_2.CheckShouldInvokeBase(this.instance))
                base.TestVirtual(str);
            else
                mTestVirtual_2.Invoke(this.instance, str);
        }

        public override void TestAbstract(System.Int32 gg)
        {
            mTestAbstract_3.Invoke(this.instance, gg);
        }

        public override System.Int32 Value
        {
            get
            {
                if (mget_Value_0.CheckShouldInvokeBase(this.instance))
                    return base.Value;
                else
                    return mget_Value_0.Invoke(this.instance);
            }
            set
            {
                if (mset_Value_1.CheckShouldInvokeBase(this.instance))
                    base.Value = value;
                else
                    mset_Value_1.Invoke(this.instance, 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;
        }
    }
}

真正的适配过程都在Adapter中,不难发现只是将所有的方法都转到了目标方法上

这个类可以通过ILRT提供的工具方法自动生成

image-20211114163155086

但我们得提前写入所需类型到对应位置

image-20211114163214460

注意事项

首先我们要知道ILRT中的跨域继承关系

image-20211218163407056

比如主工程中有一个基类:

public abstract class TestClassBase{}

同时,我们必须要一个此类的适配器

public abstract class TestClassBaseAdapter{}

此时,在热更工程中,我们可以直接继承基类

public class TestInheritance : TestClassBase{}

需要注意的是,虽然在热更工程中,我们直接继承的是TestClassBase,但是!实际上,我们直接继承的类,会是适配器类TestClassBaseAdapter,然后适配器类,再间接的继承了基类,而不是这个基类本身!!

因此,在热更工程中实现多继承时,如果继承关系中带有主工程的基类,那么很可能产生问题,需要注意。