简单委托

委托即是一种函数指针。

这里简单委托指的是,将委托的变量声明和注册都放在热更工程内进行

创建委托

委托创建的位置在主工程和热更工程内都可以,我们这里放在主工程内

ILRuntimeManager内新添加两个委托,放在最外层即可

1
2
public delegate void TestDelegateMethod(int a);
public delegate string TestDelegateFunction(int a);

委托变量声明和注册简单委托

我们先在热更工程中新建一个类,命名为TestDelegate

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
using UnityEngine;

namespace HotFix
{
public class TestDelegate
{
private TestDelegateMethod m_Method;//在热更工程内声明委托
private TestDelegateFunction m_Function;//在热更工程内声明委托

public void Initialize()
{
m_Method = MyMethod;
m_Function = MyFunction;
}

public void HotDeleInvoker(int a)
{
if (m_Method != null)
{
m_Method.Invoke(a);
}

if (m_Function != null)
{
m_Function.Invoke(a);
}
}
void MyMethod(int a)
{
Debug.LogFormat("TestDelegate Method a={0}",a);
}
string MyFunction(int a)
{
string result = a.ToString();
Debug.LogFormat("TestDelegate Function result={0}",result);
return result;
}
}
}

修改主工程ILRuntimeManagerOnHotFixLoaded

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void OnHotFixLoaded()
{

//委托调用
IType TestDeleClassType = m_AppDomain.LoadedTypes["HotFix.TestDelegate"];

object obj = ((ILType)TestDeleClassType).Instantiate();

IMethod registerMethod = TestDeleClassType.GetMethod("Initialize", null, null);
m_AppDomain.Invoke(registerMethod, obj,null);

IType intType = m_AppDomain.GetType(typeof(int));
List<IType> paramList = new List<IType>() { intType};
IMethod invokerMethod = TestDeleClassType.GetMethod("HotDeleInvoker",paramList,null);
m_AppDomain.Invoke(invokerMethod, obj,13);
}

跨域委托

跨域委托指的是,委托变量声明和注册不在同一个工程内

我们把委托变量的声明放在Unity主工程内,而把这些变量的注册放在热更工程内。

一般情况下,Unity主工程是得不到热更工程声明的委托的,更不能注册。所以把委托变量声明在Unity主工程内,注册放在热更工程内。

修改ILRuntimeManager,添加委托变量

1
2
3
4
5
6
7
8
9
public delegate void TestDelegateMethod(int a);
public delegate string TestDelegateFunction(int a);
public class ILRuntimeManager : SingletonPattern<ILRuntimeManager>
{
public TestDelegateMethod TestDeleMethod;
public TestDelegateFunction TestDeleFunction;
public System.Action<string> TestDeleAction;
//...
}

热更工程注册和调用委托

给HotFix工程的TestDelegate添加如下代码

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
public void Initialize2()
{
ILRuntimeManager.Instance.TestDeleMethod = MyMethod;
ILRuntimeManager.Instance.TestDeleFunction = MyFunction;
ILRuntimeManager.Instance.TestDeleAction = MyAction;
}
public void HotDeleInvoker2(int a)
{
if (ILRuntimeManager.Instance.TestDeleMethod != null)
{
ILRuntimeManager.Instance.TestDeleMethod.Invoke(a);
}

if (ILRuntimeManager.Instance.TestDeleFunction != null)
{
ILRuntimeManager.Instance.TestDeleFunction.Invoke(a);
}

if (ILRuntimeManager.Instance.TestDeleAction != null)
{
ILRuntimeManager.Instance.TestDeleAction.Invoke("ATAO");
}
}
void MyAction(string a)
{
Debug.LogFormat("TestDelegate Action result={0}",a);
}

修改ILRuntimeManagerOnHotFixLoaded代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void OnHotFixLoaded()
{

//委托调用
IType TestDeleClassType = m_AppDomain.LoadedTypes["HotFix.TestDelegate"];

object obj = ((ILType)TestDeleClassType).Instantiate();

IMethod registerMethod = TestDeleClassType.GetMethod("Initialize2", null, null);
m_AppDomain.Invoke(registerMethod, obj,null);

IType intType = m_AppDomain.GetType(typeof(int));
List<IType> paramList = new List<IType>() { intType};
IMethod invokerMethod = TestDeleClassType.GetMethod("HotDeleInvoker2",paramList,null);
m_AppDomain.Invoke(invokerMethod, obj,13);
}

跨域委托适配器

上面的代码直接运行是不行的,想要在ILRuntime成功注册主工程的委托,需要自己写“委托适配器”

  1. 使用系统自带的Action、Func时,委托适配器很好写

    我们在ILRuntimeManagerInitializeILRuntime方法内添加对应Action<string>的委托适配器

    1
    2
    3
    4
    5
    6
    void InitializeILRuntime()
    {
    m_AppDomain.DelegateManager.RegisterMethodDelegate<string>();

    }

    直接调用AppDomain.DelegateManager.RegisterMethodDelegate<T>(),其中的“T”可以指定这个Action的参数类型,在这里我们使用的是string

跨域委托转换器

  1. 使用自定义的Delegate时,我们需要自己将它转化为系统的Action或Func的形式

    我们在ILRuntimeManagerInitializeILRuntime方法内添加对应TestDeleMethodTestDeleFunction的委托转换器

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    void InitializeILRuntime()
    {
    m_AppDomain.DelegateManager.RegisterMethodDelegate<string>();

    //自定义委托或Unity委托注册
    m_AppDomain.DelegateManager.RegisterMethodDelegate<int>();
    m_AppDomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>(action =>
    {
    return new TestDelegateMethod((a) =>
    {
    ((System.Action<int>)action).Invoke(a);
    });
    });
    m_AppDomain.DelegateManager.RegisterFunctionDelegate<int, string>();
    m_AppDomain.DelegateManager.RegisterDelegateConvertor<TestDelegateFunction>(action =>
    {
    return new TestDelegateFunction((a) =>
    {
    return ((System.Func<int,string>)action).Invoke(a);
    });
    });
    }

    Unity自己包装好的委托,如UnityAction也要自己写转换器进行转换

    自定义委托转换器的声明是这样的:

    1
    public void RegisterDelegateConvertor<T>(Func<Delegate, Delegate> action)

    泛型T用于检测类型,传入的泛型参数必须属于System.Delegate

    参数action属于Func<Delegate, Delegate>,前一个Delegate是实现了自定义Delegate的函数的指针,后一个Delegate是进行转化后ILRuntime能用的Delegate的函数的指针。相当于告诉ILRuntime,当热更工程需要注册一个出现在主工程的委托时,使用这个适配器提供的方法对主工程的委托进行转换。

    转换器是转换器,适配器是适配器,我们想要成功注册自定义委托,还需要再在每个转换器前面添加对应的适配器

跨域委托注意事项

  1. 相同参数类型的委托,不论是自定义委托还是Action、Func,只需要添加一次委托适配器即可
  2. 尽量避免不必要的跨域委托调用
  3. 尽量使用Action和Func

跨域委托补充

前面说到,UnityAction也要自己写转换器进行转换,这里补充一些

比如UGUI的Toggle组件,使用的是UnityAction<bool>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void InitializeILRuntime()
{
//...

//UnityAction委托注册
m_AppDomain.DelegateManager.RegisterMethodDelegate<bool>();
m_AppDomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction<bool>>(action =>
{
return new UnityEngine.Events.UnityAction<bool>((a) =>
{
((System.Action<bool>)action).Invoke(a);
});
});
}

在实际使用中,m_AppDomain.DelegateManager.RegisterMethodDelegatem_AppDomain.DelegateManager.RegisterFunctionDelegate都会写在最前面,适配器写在一块儿能比较清楚地看到有没有重复的。

虽然跨域委托的调用我们写在了热更工程里,不过我们写在主工程里也是一样的,重点是在委托注册的时候注意跨域的问题。