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












發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章