跨域继承

跨域继承,即是热更工程继承主工程里面的类。

首先在ILRuntimeManager添加一个抽象类

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class TestBaseClass
{
public virtual void TestVirtual(string str)
{
Debug.Log("TestBaseClass TestVirtual str=" + str);
}
public abstract void TestAbstract(int a);
public virtual int Value
{
get { return 0; }
}
}

然后再在HotFix热更工程里添加TestInheritance新类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using UnityEngine;

namespace HotFix
{
public class TestInheritance : TestBaseClass
{
public override void TestVirtual(string str)
{
base.TestVirtual(str);
Debug.Log("TestInheriance TestVirtual str = " + str);
}
public override void TestAbstract(int a)
{
Debug.Log("TestInheritance TestAbstract a = " + a);
}
public override int Value => base.Value;
}
}

修改ILRuntimeManagerOnHotFixLoaded方法

1
2
3
4
5
void OnHotFixLoaded()
{
//跨域继承
TestBaseClass obj = m_AppDomain.Instantiate<TestBaseClass>("HotFix.TestInheritance");
}

跨域继承适配器

跨域继承适配器需要两个类,一个是跨域绑定类,一个是适配器类

跨域继承适配器绑定类

绑定类属于工具类,帮助ILRuntime在运行时获取到主程序集里面需要被继承的类(通过BaseCLRType属性),以及需要被继承的类的适配器(通过AdaptorType属性),以及提供一个适配器实例的方法(通过CreateCLRInstance方法)。

ILRuntimeManager里面新建一个InheritanceAdaptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public class InheritanceAdaptor : CrossBindingAdaptor
{
/// <summary>
/// 想要被继承的类
/// </summary>
public override System.Type BaseCLRType
{
get
{
//如果此基类需要实现多个接口,这里return null,使用下面的BaseCLRTypes
return typeof(TestBaseClass);
}
}
/// <summary>
/// 实际的适配器类
/// </summary>
public override System.Type AdaptorType
{
get
{
return typeof(Adaptor);
}
}
/// <summary>
/// 如果被继承的类有多个接口,使用这个
/// </summary>
public override System.Type[] BaseCLRTypes
{
get
{
//跨域继承只能有1个Adaptor,因此应该尽量避免一个类同时实现多个外部接口
//ILRuntime虽然支持同时实现多个外部接口,但是可能会出现不可预期的问题
//日常开发如果要实现多个接口,请在Unity这边先做一个基类实现这些接口,然后继承这个基类
//如果这个Adaptor必须要实现多个接口,示例如下:
//return new System.Type[] { typeof(IEnumerator<object>), typeof(IEnumerator), typeof(System.IDisposable) };
return null;
}
}
public override object CreateCLRInstance(AppDomain appdomain, ILTypeInstance instance)
{
return new Adaptor(appdomain,instance);
}
class Adaptor : TestBaseClass, CrossBindingAdaptorType
{
//...
}
}

适配器类

适配器类一般是适配器绑定类的内部类,也就是上面的class Adaptor

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
public class Adaptor : TestBaseClass, CrossBindingAdaptorType
{
private AppDomain m_AppDomain;
private ILTypeInstance m_ILTypeInstance;
private IMethod m_TestAbstractMethod;//防止方法重复反射获取,提取全局变量
private IMethod m_TestVirtualMethod;//防止方法重复反射获取,提取全局变量
private IMethod m_GetValue;//防止方法重复反射获取,提取全局变量
private IMethod m_ToStringMethod;//防止方法重复反射获取,提取全局变量
object[] param1 = new object[1];//防止方法参数重复创建产生GC,提取全局变量
private bool m_IsTestVirtualInvoking = false;//虚方法的标识位,防止循环调用
private bool m_IsGetValueInvoking = false;//虚方法的标识位,防止循环调用
public Adaptor() { }

public Adaptor(AppDomain appdomain, ILTypeInstance instance)
{
m_AppDomain = appdomain;
m_ILTypeInstance = instance;
}

public ILTypeInstance ILInstance
{
get
{
return m_ILTypeInstance;
}
}
//在适配器中,重新实现所有在热更工程中继承的子类的方法
//并且将控制权转移到热更工程子类的方法中
public override void TestAbstract(int a)
{
if (m_TestAbstractMethod == null)
{
m_TestAbstractMethod = m_ILTypeInstance.Type.GetMethod("TestAbstract",1);
}

if (m_TestAbstractMethod != null)
{
param1[0] = a;
m_AppDomain.Invoke(m_TestAbstractMethod, m_ILTypeInstance, param1);
}
}
public override void TestVirtual(string str)
{
if (m_TestVirtualMethod == null)
{
m_TestVirtualMethod = m_ILTypeInstance.Type.GetMethod("TestVirtual", 1);
}
//虚函数必须要设定一个标识位来表示当前是否在调用中
//因为虚函数在热更类的override可能会调用base.TestVirtual(),跨域继承并不是真正的继承,会导致重复调用
if (m_TestVirtualMethod != null && !m_IsTestVirtualInvoking)
{
m_IsTestVirtualInvoking = true;
param1[0] = str;
m_AppDomain.Invoke(m_TestVirtualMethod, m_ILTypeInstance, param1);
m_IsTestVirtualInvoking = false;
}
else
{
base.TestVirtual(str);
}
}
public override int Value
{
get
{
if(m_GetValue == null)
{
m_GetValue = m_ILTypeInstance.Type.GetMethod("get_Value");
}
if(m_GetValue != null && !m_IsGetValueInvoking)
{
m_IsGetValueInvoking = true;
int res = (int)m_AppDomain.Invoke(m_GetValue, m_ILTypeInstance);
m_IsGetValueInvoking = false;
return res;
}
else
{
return base.Value;
}
}
}
/// <summary>
/// 重载ToString方法,否则的话调用的是Adaptor的ToString,不是热更类的ToString
/// </summary>
public override string ToString()
{
if (m_ToStringMethod == null)
{
m_ToStringMethod = m_AppDomain.ObjectType.GetMethod("ToString",0);//基础Object通过这种方式获取方法
}
IMethod m = m_ILTypeInstance.Type.GetVirtualMethod(m_ToStringMethod);
if (m != null || m is ILMethod)//检测ToString方法是否被热更类重载过
{
return m_ILTypeInstance.ToString();
}
else
{
return m_ILTypeInstance.Type.FullName;
}

}
}

适配器类理解

ILRuntime在运行时只能获取到热更dll的字节码,这意味着它无权获取到主工程里面的类,所以在热更类继承主工程的类属于“假继承”。

这就是我们写适配器类的原因,适配器类是Unity主工程内真正继承对应基类的子类,只不过这个类变相调用了热更中对应的,也继承了基类的子类,有了这个中转,我们才能模拟出“继承”的效果。

这样我们在主工程内调用热更工程中子类的方法,ILRuntime会查找这个适配器,然后调用适配器。

一般来讲,适配器类只需要写明基类里面的虚方法或抽象方法的逻辑,其余的基类里不需要子类覆盖的方法和属性等不需要写进适配器里。

只要子类重载方法里有调用base.的地方,在适配器里面就必须声明好“m_IsInvoking”变量。

调用子类

跨域继承适配器写好后,我们在ILRuntimeManagerInitializeILRuntime方法中注册这个适配器,就像之前注册委托适配器一样。

1
2
3
4
5
6
void InitializeILRuntime()
{
//...
//跨域继承绑定类注册
m_AppDomain.RegisterCrossBindingAdaptor(new InheritanceAdaptor());
}

最后在ILRuntimeManagerOnHotFixLoaded方法中调用子类即可

1
2
3
4
5
6
7
8
void OnHotFixLoaded()
{
//跨域继承
TestBaseClass obj = m_AppDomain.Instantiate<TestBaseClass>("HotFix.TestInheritance");
obj.TestAbstract(556);
obj.TestVirtual("ATAO");
Debug.Log(obj.Value);
}

第二种调用方法

在上面的调用中可以发现,我们通过跨域继承直接调用基类的方式来调用热更工程里面子类的方法。

我们可以在子类中写一个静态方法,像单例模式一样,用于返回子类的实例

修改HotFix工程的TestInheritance

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using UnityEngine;

namespace HotFix
{
public class TestInheritance : TestBaseClass
{
public static TestInheritance GetInstance()
{
return new TestInheritance();
}
//...
}
}

这样的话,我们修改ILRuntimeManagerOnHotFixLoaded方法,使用AppDomain.Invoke这个API来获取实例

1
2
3
4
5
6
7
8
void OnHotFixLoaded()
{
//跨域继承
TestBaseClass obj = m_AppDomain.Invoke("HotFix.TestInheritance","GetInstance",null,null) as TestBaseClass;
obj.TestAbstract(556);
obj.TestVirtual("ATAO");
Debug.Log(obj.Value);
}