ILRuntime第三课Delegate

​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
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using ILRuntime.Runtime.Enviorment;
using System.IO;
using ILRuntime.CLR.Method;
 
public delegate void TestDelegateMethod(int a);
public delegate string TestDelegateFunction(int a);
 
public class DelegateDemo : MonoBehaviour {
    public static TestDelegateMethod TestMethodDelegate;
    public static TestDelegateFunction TestFunctionDelegate;
    public static System.Action<string> TestActionDelegate;
 
    //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_TestProject.dll");
#else
        WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_TestProject.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_TestProject.pdb");
#else
        www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_TestProject.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()
    {
        //下面这些注册代码,正式使用的时候,应该写在InitializeILRuntime中
        //TestDelegateMethod, 这个委托类型为有个参数为int的方法,注册仅需要注册不同的参数搭配即可
        appdomain.DelegateManager.RegisterMethodDelegate<int>();
        //带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个
        appdomain.DelegateManager.RegisterFunctionDelegate<intstring>();
        //Action<string> 的参数为一个string
        appdomain.DelegateManager.RegisterMethodDelegate<string>();
 
        //TestDelegateMethod委托转换器
        appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>((act) =>
        {
            return new TestDelegateMethod((a) =>
            {
                ((System.Action<int>)act)(a);
            });
        });
 
        appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateFunction>((act) =>
        {
            return new TestDelegateFunction((a) =>
            {
                return ((System.Func<int,string>)act)(a);
            });
        });
 
 
    }
 
    void OnHotFixLoaded() {
        Debug.Log("完全在热更DLL内部使用的委托,直接可用,不需要做任何处理");
        appdomain.Invoke("HotFix_TestProject.DelegateTest""Initialize",null,null);
        appdomain.Invoke("HotFix_TestProject.DelegateTest""RunTest"null,null);
 
        Debug.Log("如果需要跨域调用委托(将热更DLL里面的委托实例传到Unity主工程用), 就需要注册适配器,不然就会像下面这样");
 
        try {
            //适配器错误会消失,因为在InitializeILRuntime中写了委托适配器,但是会报转换器的错误
            appdomain.Invoke("HotFix_TestProject.DelegateTest""Initialize2"nullnull);
            appdomain.Invoke("HotFix_TestProject.DelegateTest""RunTest2"nullnull);
        }
        catch (System.Exception ex) {
            Debug.LogError(ex.ToString());
        }
 
        //为了演示,清除适配器缓存,实际使用中不要这么做
        //ClearDelegateCache();
        //Debug.Log("这是因为iOS的IL2CPP模式下,不能动态生成类型,为了避免出现不可预知的问题,我们没有通过反射的方式创建委托实例,因此需要手动进行一些注册");
        //Debug.Log("首先需要注册委托适配器,刚刚的报错的错误提示中,有提示需要的注册代码");
        //Debug.Log("ILRuntime内部是用Action和Func这两个系统内置的委托类型来创建实例的,所以其他的委托类型都需要写转换器");
        //Debug.Log("将Action或者Func转换成目标委托类型");
 
        Debug.Log("我们再来在Unity主工程中调用一下刚刚的委托试试");
        TestMethodDelegate(789);
        var str = TestFunctionDelegate(098);
        Debug.Log("!! OnHotFixLoaded str = " + str);
        TestActionDelegate("Hello From Unity Main Project");
 
    }
 
    void ClearDelegateCache() {
        var type = appdomain.LoadedTypes["HotFix_TestProject.DelegateTest"];
        ILMethod m = type.GetMethod("Method", 1) as ILMethod;
        m.DelegateAdapter = null;
 
        m = type.GetMethod("Function", 1) as ILMethod;
        m.DelegateAdapter = null;
 
        m = type.GetMethod("Action", 1) as ILMethod;
        m.DelegateAdapter = null;
    }
}

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