启动HotFix
- 使用AppDomain加载热更程序集dll及pdb(正式发布时请将PDB去掉)
- 初始化ILRuntime,(设置ILRuntime主线程的线程ID ,监听调试端口等),然后做一些ILRuntime的注册
- 使用热更
调用Invocation
//1.通过AppDomain调用方法
//参数:类型全名(命名空间+类型)、方法名、实例、实参列表
appdomain.Invoke("XX.XXX", "XXXX", null, null);
//2.通过IMethod调用方法
//预先获得IMethod,可以减低每次调用查找方法耗用的时间
IType type = appdomain.LoadedTypes["XX.XXX"];
//根据方法名称和参数个数获取方法
IMethod method = type.GetMethod("XXXX", 1);
//使用IMethod调用(参数:IMethod、实例、实参列表)
appdomain.Invoke(method, null, 123);
//3.通过无GC Alloc方式调用方法
//无GC Alloc方式参数压入顺序:先将带ref/out关键字的形参参数按顺序压入初始值->若为成员方法压入this->按形参顺序压入实参
using (var ctx = appdomain.BeginInvoke(method))
{
//将参数进行压栈
ctx.PushInteger(123);
//调用
ctx.Invoke();
}
//4.指定参数类型来获得IMethod
IType intType = appdomain.GetType(typeof(int));
//参数类型列表
List<IType> paramList = new List<ILRuntime.CLR.TypeSystem.IType>();
paramList.Add(intType);
//根据方法名称和参数类型列表获取方法(也可加入泛型参数类型列表获取泛型方法)
method = type.GetMethod("XXXX", paramList, null);
appdomain.Invoke(method, null, 456);
//5.调用成员方法
//实例化(参数:类型全名、构造参数)
object obj = appdomain.Instantiate("XX.XXX", new object[] { 233 });
//通过ILType实例化
object obj2 = ((ILType)type).Instantiate();
//调用成员方法(属性器Get方法)
method = type.GetMethod("get_XXXXX", 0);
using (var ctx = appdomain.BeginInvoke(method))
{
//将对象压栈后调用
ctx.PushObject(obj);
ctx.Invoke();
//获取返回值
int id = ctx.ReadInteger();
}
using (var ctx = appdomain.BeginInvoke(method))
{
//将对象压栈后调用
ctx.PushObject(obj2);
ctx.Invoke();
//获取返回值
int id = ctx.ReadInteger();
}
//6.调用泛型方法
IType stringType = appdomain.GetType(typeof(string));
IType[] genericArguments = new IType[] { stringType };
//参数:类型全名(命名空间+类型)、方法名、泛型参数类型列表、实例、实参列表
appdomain.InvokeGenericMethod("XX.XXX", "XXXX", genericArguments, null, "TestString");
//7.获取泛型方法的IMethod
paramList.Clear();
paramList.Add(intType);
genericArguments = new IType[] { intType };
//根据参数类型列表及泛型参数类型列表来获取对应IMthod
method = type.GetMethod("XXXX", paramList, genericArguments);
appdomain.Invoke(method, null, 33333);
//8.通过无GC Alloc方式调用带Ref/Out参数的方法
//被调用方法:public void RefOutMethod(int addition, out List<int> lst, ref int val)
method = type.GetMethod("RefOutMethod", 3);
int initialVal = 500;
using(var ctx = appdomain.BeginInvoke(method))
{
//第一个ref/out参数初始值
ctx.PushObject(null);
//第二个ref/out参数初始值
ctx.PushInteger(initialVal);
//压入this
ctx.PushObject(obj);
//压入参数1:addition
ctx.PushInteger(100);
//压入参数2: lst,由于是ref/out,需要压引用,这里是引用0号位,也就是第一个PushObject的位置
ctx.PushReference(0);
//压入参数3,val,同ref/out
ctx.PushReference(1);
ctx.Invoke();
//读取0号位的值
List<int> lst = ctx.ReadObject<List<int>>(0);
initialVal = ctx.ReadInteger(1);
}
委托Delegate
在ILRuntime中,若需要跨域调用委托,则需要注册适配器。
使用Action或者Func当作委托类型可以避免写转换器,但需要注册参数搭配。
完全在热更DLL内部使用的委托,直接可用,不需要做任何处理
//注册委托参数搭配
appdomain.DelegateManager.RegisterMethodDelegate<int>();
appdomain.DelegateManager.RegisterFunctionDelegate<int, string>();
如需要注册适配器,则在注册参数代码后注册转换器,将Action或者Func转换成目标委托类型即可。
//注册适配转换器
appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction<float>>((action) =>
{
return new UnityEngine.Events.UnityAction<float>((a) =>
{
((System.Action<float>)action)(a);
});
});
继承Inheritance
跨域继承需要生成适配器,并且在ILRuntime初始化完成后进行注册。
//参数为所需跨域继承类型的适配器实例
appdomain.RegisterCrossBindingAdaptor(new XXXAdapter());
在自己实现的ILRuntime.Moudle中添加了跨域继承类型的配置功能。
CLR重定向CLRRedirection
当我们需要挟持原方法实现,添加一些热更DLL中的特殊处理的时候,就需要CLR重定向了。
//获取参数类型符合要求的MethodInfo
var mi = typeof(Debug).GetMethod("Log", new System.Type[] { typeof(object) });
//参数:要重定向的方法的MethodInfo、重定向到的方法
appdomain.RegisterCLRMethodRedirection(mi, Log_11);
CLR绑定CLRBinding
默认情况下,从热更DLL里调用Unity主工程的方法,是通过反射的方式调用的,这个过程中会产生GC Alloc,并且执行效率会偏低。
ILRuntime通过CLR方法绑定机制,可以选择性的对经常使用的CLR接口进行直接调用,从而尽可能的消除反射调用开销以及额外的GC Alloc。
//在生成CLRBinding代码后,在ILRuntime CLR重定向注册之后调用以下方法,即可使用CLR绑定
ILRuntime.Runtime.Generated.CLRBindings.Initialize(appDomain);
协程Coroutine
使用Couroutine时,C#编译器会自动生成一个实现了IEnumerator,IEnumerator,IDisposable接口的类,因为这是跨域继承,所以需要写CrossBindAdapter。
跨域继承只能有1个Adapter,因此应该尽量避免一个类同时实现多个外部接口,对于coroutine来说是IEnumerator,IEnumerator和IDisposable。
ILRuntime虽然支持,但是一定要小心这种用法,使用不当很容易造成不可预期的问题。
日常开发如果需要实现多个DLL外部接口,请在Unity这边先做一个基类实现那些个接口,然后继承那个基类。
使用了插件作者的CoroutineAdapter,尝试了以下,可以在热更dll内随意使用协程,但是CoroutineAdapter无法进行CLR绑定。
MonoBehaviour
在热更DLL里面使用MonoBehaviour是可以做到的。
需要在MonoBehaviourAdaptor中实现对应的事件方法。
为了在热更DLL里正常使用AddComponent和GetComponent需要重定向这两个方法。
也可以实现通过MonoBehaviourAdaptor在Unity获取到热更dll类型。
反射Reflection
C#工程中反射是一个非常经常用到功能,ILRuntime也对反射进行了支持,在热更DLL中使用反射跟原生C#没有任何区别。
在Unity中使用AppDomain获取DLL中类型后,使用IType的ReflectionType可获取热更DLL内的真正类型,之后正常使用反射调用方法,字段,属性即可。
序列化LitJson
非常简单的序列化反序列化,但是对LitJson源码是需要进行处理的。这里插件作者已经整理好了。
值类型绑定ValueTypeBinding
值类型绑定内部对值类型的操作进行了重定向。