Unity 热更新之ILRuntime(三)
一.ILRuntime跨域继承
- 如果你想在热更DLL项目当中继承一个Unity主工程里的类,或者实现一个主工程里的接口,你需要在Unity主工程中实现一个继承适配器。
如果不进行处理,那么运行之后将会报错,所以需要进行跨域继承
- 相关文档
- http://ourpalm.github.io/ILRuntime/public/v1/guide/cross-domain.html
public class UIBaseAdapter : CrossBindingAdaptor
{
public override Type BaseCLRType
{
get { return typeof(UIBase); }
}
public override Type AdaptorType
{
get { return typeof(Adaptor); }
}
public override Type[] BaseCLRTypes
{
get { return null; }
}
public override object CreateCLRInstance(AppDomain appdomain, ILTypeInstance instance)
{
return new Adaptor(appdomain, instance);
}
class Adaptor : UIBase, CrossBindingAdaptorType
{
private AppDomain appdomain;
private ILTypeInstance instance;
public Adaptor(AppDomain appdomain, ILTypeInstance instance)
{
this.appdomain = appdomain;
this.instance = instance;
}
public ILTypeInstance ILInstance => instance;
private bool mID;
private IMethod mMetID;
private bool mIDInvoking;
public override int ID
{
get
{
if (mID == false)
{
mMetID = instance.Type.GetMethod("get_ID", 0);
mID = true;
}
if (mMetID != null && !mIDInvoking)
{
mIDInvoking = true;
var res = (int)appdomain.Invoke(mMetID, instance, null);
mIDInvoking = false;
return res;
}
else
return base.ID;
}
}
private bool mOpen;
private IMethod mMetOpen;
private bool mOpenInvoking;
private readonly object[] mOpenParame = new object[1];
public override void Open(string str)
{
if (mOpen == false)
{
mMetOpen = instance.Type.GetMethod("Open", 1);
mOpen = true;
}
if (mMetOpen != null && !mOpenInvoking)
{
mOpenInvoking = true;
mOpenParame[0] = str;
appdomain.Invoke(mMetOpen, instance, mOpenParame);
mOpenInvoking = false;
}
else
base.Open(str);
}
}
}
二.CLR重定向
- 最好的例子就是Unity的Debug.Log接口了,默认情况下,如果在DLL工程中调用该接口,是没有办法显示正确的调用堆栈的,会给开发带来一些麻烦,下面我会展示怎么通过CLR重定向来实现在Debug.Log调用中打印热更DLL中的调用堆栈
public unsafe static StackObject* DLog(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj)
{
ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
StackObject* ptr_of_this_method;
//只有一个参数,所以返回指针就是当前栈指针ESP - 1
StackObject* __ret = ILIntepreter.Minus(__esp, 1);
//第一个参数为ESP -1, 第二个参数为ESP - 2,以此类推
ptr_of_this_method = ILIntepreter.Minus(__esp, 1);
//获取参数message的值
object message = StackObject.ToObject(ptr_of_this_method, __domain, __mStack);
//需要清理堆栈
__intp.Free(ptr_of_this_method);
//如果参数类型是基础类型,例如int,可以直接通过int param = ptr_of_this_method->Value获取值,
//关于具体原理和其他基础类型如何获取,请参考ILRuntime实现原理的文档。
//通过ILRuntime的Debug接口获取调用热更DLL的堆栈
string stackTrace = __domain.DebugService.GetStackTrace(__intp);
Debug.Log(string.Format("{0}\n==========================\n{1}", message, stackTrace));
return __ret;
}
然后在通过下面的代码注册重定向即可
private unsafe void RegisterCLRMethodRedirection()
{
_appDomain.RegisterCLRMethodRedirection(typeof(Debug).GetMethod("Log",new Type[]{typeof(object)}),DLog);
}
三.CLR绑定
-
优点
-
通常情况下,如果要从热更DLL中调用Unity主工程或者Unity的接口,是需要通过反射接口来调用的,包括市面上不少其他热更方案,也是通过这种方式来对CLR方接口进行调用的。
-
但是这种方式有着明显的弊端,最突出的一点就是通过反射来调用接口调用效率会比直接调用低很多,再加上反射传递函数参数时需要使用object[]数组,这样不可避免的每次调用都会产生不少GC Alloc。众所周知GC Alloc高意味着在Unity中执行会存在较大的性能问题。
-
ILRuntime通过CLR方法绑定机制,可以选择性的对经常使用的CLR接口进行直接调用,从而尽可能的消除反射调用开销以及额外的GC Alloc
-
注意
在CLR绑定代码生成之后,需要将这些绑定代码注册到AppDomain中才能使CLR绑定生效,但是一定要记得将CLR绑定的注册写在CLR重定向的注册后面,因为同一个方法只能被重定向一次,只有先注册的那个才能生效。