ILRuntime第四课Inheritance

来源:互联网 发布:车辆mod修改软件 编辑:程序博客网 时间:2024/05/16 05:46

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












原创粉丝点击