ILRuntime|03 调试、委托、继承
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,再执行
继承
当想在热更中继承主工程的类时,依然需要一个适配器
在主工程有如下类
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提供的工具方法自动生成
但我们得提前写入所需类型到对应位置
注意事项
首先我们要知道ILRT中的跨域继承关系
比如主工程中有一个基类:
public abstract class TestClassBase{}
同时,我们必须要一个此类的适配器
public abstract class TestClassBaseAdapter{}
此时,在热更工程中,我们可以直接继承基类
public class TestInheritance : TestClassBase{}
需要注意的是,虽然在热更工程中,我们直接继承的是TestClassBase,但是!实际上,我们直接继承的类,会是适配器类TestClassBaseAdapter,然后适配器类,再间接的继承了基类,而不是这个基类本身!!
因此,在热更工程中实现多继承时,如果继承关系中带有主工程的基类,那么很可能产生问题,需要注意。