在DLL热更中,如果需要继承主项目中的类或者接口的话,需要为其写一个适配器
1.主工程
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
using
UnityEngine;
using
System.Collections;
using
System.Collections.Generic;
using
System.IO;
using
ILRuntime.CLR.TypeSystem;
using
ILRuntime.CLR.Method;
using
ILRuntime.Runtime.Enviorment;
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
Inheritance : MonoBehaviour
{
//AppDomain是ILRuntime的入口,最好是在一个单例类中保存,整个游戏全局就一个,这里为了示例方便,每个例子里面都单独做了一个
//大家在正式项目中请全局只创建一个AppDomain
AppDomain appdomain;
void
Start()
{
StartCoroutine(LoadHotFixAssembly());
}
IEnumerator LoadHotFixAssembly()
{
//首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒
appdomain =
new
ILRuntime.Runtime.Enviorment.AppDomain();
//正常项目中应该是自行从其他地方下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示方便直接从StreammingAssets中读取,
//正式发布的时候需要大家自行从其他地方读取dll
//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
//这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝
#if UNITY_ANDROID
WWW www =
new
WWW(Application.streamingAssetsPath +
"/HotFix_Project.dll"
);
#else
WWW www =
new
WWW(
"file:///"
+ Application.streamingAssetsPath +
"/HotFix_Project.dll"
);
#endif
while
(!www.isDone)
yield
return
null
;
if
(!
string
.IsNullOrEmpty(www.error))
UnityEngine.Debug.LogError(www.error);
byte
[] dll = www.bytes;
www.Dispose();
//PDB文件是调试数据库,如需要在日志中显示报错的行号,则必须提供PDB文件,不过由于会额外耗用内存,正式发布时请将PDB去掉,下面LoadAssembly的时候pdb传null即可
#if UNITY_ANDROID
www =
new
WWW(Application.streamingAssetsPath +
"/HotFix_Project.pdb"
);
#else
www =
new
WWW(
"file:///"
+ Application.streamingAssetsPath +
"/HotFix_Project.pdb"
);
#endif
while
(!www.isDone)
yield
return
null
;
if
(!
string
.IsNullOrEmpty(www.error))
UnityEngine.Debug.LogError(www.error);
byte
[] pdb = www.bytes;
using
(System.IO.MemoryStream fs =
new
MemoryStream(dll))
{
using
(System.IO.MemoryStream p =
new
MemoryStream(pdb))
{
appdomain.LoadAssembly(fs, p,
new
Mono.Cecil.Pdb.PdbReaderProvider());
}
}
InitializeILRuntime();
OnHotFixLoaded();
}
void
InitializeILRuntime()
{
//这里做一些ILRuntime的注册,这里应该写继承适配器的注册,为了演示方便,这个例子写在OnHotFixLoaded了
}
void
OnHotFixLoaded()
{
Debug.Log(
"首先我们来创建热更里的类实例"
);
TestClassBase obj;
try
{
obj = appdomain.Instantiate<TestClassBase>(
"HotFix_Project.TestInheritance"
);
}
catch
(System.Exception ex)
{
Debug.LogError(ex.ToString());
}
Debug.Log(
"Oops, 报错了,因为跨域继承必须要注册适配器。 如果是热更DLL里面继承热更里面的类型,不需要任何注册。"
);
Debug.Log(
"所以现在我们来注册适配器"
);
appdomain.RegisterCrossBindingAdaptor(
new
InheritanceAdapter());
Debug.Log(
"现在再来尝试创建一个实例"
);
obj = appdomain.Instantiate<TestClassBase>(
"HotFix_Project.TestInheritance"
);
Debug.Log(
"现在来调用成员方法"
);
obj.TestAbstract(123);
obj.TestVirtual(
"Hello"
);
Debug.Log(
"现在换个方式创建实例"
);
obj = appdomain.Invoke(
"HotFix_Project.TestInheritance"
,
"NewObject"
,
null
,
null
)
as
TestClassBase;
obj.TestAbstract(456);
obj.TestVirtual(
"Foobar"
);
}
void
Update()
{
}
}
|
2.适配器
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
|
using
ILRuntime.Runtime.Enviorment;
using
System;
using
System.Collections.Generic;
using
ILRuntime.Runtime.Intepreter;
using
ILRuntime.CLR.Method;
public
class
ClassInheritanceAdaptor : CrossBindingAdaptor
{
public
override
Type BaseCLRType
{
get
{
//想继承的类
return
typeof
(TestClassBase);
//若想一个DLL类实现多个主工程中的接口,则return null
}
}
public
override
Type[] BaseCLRTypes
{
get
{
//跨域继承只能有1个Adapter,因此应该尽量避免一个类同时实现多个外部接口,
//ILRuntime虽然支持同时实现多个接口,但是一定要小心这种用法,使用不当很容易造成不可预期的问题
//日常开发如果需要实现多个DLL外部接口,请在Unity这边先做一个基类实现那些个接口,然后继承那个基类
//如需一个Adapter实现多个接口,请用下面这行
//return new Type[] { typeof(IEnumerator<object>), typeof(IEnumerator), typeof(IDisposable) };
return
null
;
}
}
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;
}
}
}
|
3.DLL热更中继承
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
|
using
System;
using
System.Collections.Generic;
namespace
HotFix_Project
{
//一定要特别注意,:后面只允许有1个Unity主工程的类或者接口,但是可以有随便多少个热更DLL中的接口
public
class
TestInheritance : TestClassBase
{
public
override
void
TestAbstract(
int
gg)
{
UnityEngine.Debug.Log(
"!! TestInheritance.TestAbstract gg ="
+ gg);
}
public
override
void
TestVirtual(
string
str)
{
base
.TestVirtual(str);
UnityEngine.Debug.Log(
"!! TestInheritance.TestVirtual str ="
+ str);
}
public
static
TestInheritance NewObject()
{
return
new
HotFix_Project.TestInheritance();
}
}
}
|