ILRuntime第四课Inheritance

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












  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值