官方示例下载地址1: https://github.com/Ourpalm/ILRuntime
官方示例下载地址2 : https://github.com/Ourpalm/ILRuntimeU3D
官方文档地址 : https://ourpalm.github.io/ILRuntime/public/v1/guide/tutorial.html
一.Invocation案例
void OnHotFixLoaded()
{
Debug.Log("调用无参数静态方法");
//调用无参数静态方法,appdomain.Invoke("命名空间.类名", "方法名", 对象引用, 参数列表);
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);
//调用带参数的静态方法
Debug.Log("调用带参数的静态方法");
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest2", null, 123);
Debug.Log("通过IMethod调用方法");
//预先获得IMethod,可以减低每次调用查找方法耗用的时间
IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
//根据方法名称和参数个数获取方法
IMethod method = type.GetMethod("StaticFunTest", 0);
appdomain.Invoke(method, null, null);
Debug.Log("指定参数类型来获得IMethod");
IType intType = appdomain.GetType(typeof(int));
//参数类型列表
List<IType> paramList = new List<ILRuntime.CLR.TypeSystem.IType>();
paramList.Add(intType);
//根据方法名称和参数类型列表获取方法
method = type.GetMethod("StaticFunTest2", paramList, null);
appdomain.Invoke(method, null, 456);
Debug.Log("实例化热更里的类");
object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 });
//第二种方式
object obj2 = ((ILType)type).Instantiate();
Debug.Log("调用成员方法");
int id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj, null);
Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id);
id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj2, null);
Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id);
Debug.Log("调用泛型方法");
IType stringType = appdomain.GetType(typeof(string));
IType[] genericArguments = new IType[] { stringType };
appdomain.InvokeGenericMethod("HotFix_Project.InstanceClass", "GenericMethod", genericArguments, null, "TestString");
Debug.Log("获取泛型方法的IMethod");
paramList.Clear();
paramList.Add(intType);
genericArguments = new IType[] { intType };
method = type.GetMethod("GenericMethod", paramList, genericArguments);
appdomain.Invoke(method, null, 33333);
}
写的非常详细了,一路看下来只有一个地方不明白的
object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 });
这个是调用含参构造函数进行实例化,可以看到 InstanceClass有这样一个构造函数:
public InstanceClass(int id)
{
UnityEngine.Debug.Log("!!! InstanceClass::InstanceClass() id = " + id);
this.id = id;
}
二.Delegate案例学习
这个案例带了错误的示例,所以我把不必要的清除了
public static TestDelegateMethod TestMethodDelegate;
public static TestDelegateFunction TestFunctionDelegate;
public static System.Action<string> TestActionDelegate;
void OnHotFixLoaded()
{
Debug.Log("完全在热更DLL内部使用的委托,直接可用,不需要做任何处理");
appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize", null, null);
appdomain.Invoke("HotFix_Project.TestDelegate", "RunTest", null, null);
Debug.Log("首先需要注册委托适配器");
//下面这些注册代码,正式使用的时候,应该写在InitializeILRuntime中
//TestDelegateMethod, 这个委托类型为有个参数为int的方法,注册仅需要注册不同的参数搭配即可
appdomain.DelegateManager.RegisterMethodDelegate<int>();
//Action<string> 的参数为一个string
appdomain.DelegateManager.RegisterMethodDelegate<string>();
//带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个
appdomain.DelegateManager.RegisterFunctionDelegate<int, string>();
Debug.Log("ILRuntime内部是用Action和Func这两个系统内置的委托类型来创建实例的,所以其他的委托类型都需要写转换器");
Debug.Log("将Action或者Func转换成目标委托类型");
appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>((action) =>
{
//转换器的目的是把Action或者Func转换成正确的类型,这里则是把Action<int>转换成TestDelegateMethod
return new TestDelegateMethod((a) =>
{
//调用委托实例
((System.Action<int>)action)(a);
});
});
//对于TestDelegateFunction同理,只是是将Func<int, string>转换成TestDelegateFunction
appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateFunction>((action) =>
{
return new TestDelegateFunction((a) =>
{
return ((System.Func<int, string>)action)(a);
});
});
//下面再举一个这个Demo中没有用到,但是UGUI经常遇到的一个委托,例如UnityAction<float>
appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction<float>>((action) =>
{
return new UnityEngine.Events.UnityAction<float>((a) =>
{
((System.Action<float>)action)(a);
});
});
Debug.Log("现在我们再来运行一次");
appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize2", null, null);
appdomain.Invoke("HotFix_Project.TestDelegate", "RunTest2", null, null);
Debug.Log("运行成功,我们可以看见,用Action或者Func当作委托类型的话,可以避免写转换器,所以项目中在不必要的情况下尽量只用Action和Func");
TestActionDelegate("Hello From Unity Main Project");
Debug.Log("另外应该尽量减少不必要的跨域委托调用,如果委托只在热更DLL中用,是不需要进行任何注册的");
Debug.Log("---------");
Debug.Log("我们再来在Unity主工程中调用一下刚刚的委托试试");
TestMethodDelegate(789);
var str = TestFunctionDelegate(098);
Debug.Log("!! OnHotFixLoaded str = " + str);
}
总得来说 :
1.调用完全在热更DLL内部使用的委托,直接可用,不需要做任何处理
2.将热更DLL里面的委托实例传到Unity主工程用 : 注册委托适配器 - 转换器(如果委托是Func或者Action就不需要转换) - 调用
三.Inheritance(跨域继承)案例
Unity主工程中被继承的类 :
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 InheritanceAdapter : CrossBindingAdaptor
{
public override Type BaseCLRType
{
get
{
return typeof(TestClassBase);//这是你想继承的那个类
}
}
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;
}
}
}
调用 :
void OnHotFixLoaded()
{
Debug.Log("所以现在我们来注册适配器");
appdomain.RegisterCrossBindingAdaptor(new InheritanceAdapter());
Debug.Log("现在再来尝试创建一个实例");
//TestClassBase : 继承基类 HotFix_Project.TestInheritance : 子类
obj = appdomain.Instantiate<TestClassBase>("HotFix_Project.TestInheritance");
Debug.Log("现在来调用成员方法");
obj.TestAbstract(123);
obj.TestVirtual("Hello");
Debug.Log("现在换个方式创建实例");
//NewObject : 创建TestInheritance类的方法
obj = appdomain.Invoke("HotFix_Project.TestInheritance", "NewObject", null, null) as TestClassBase;
obj.TestAbstract(456);
obj.TestVirtual("Foobar");
}
这里面主要的难点在于创建类的继承适配器,总结下大致规则
1.继承CrossBindingAdaptor
2.实现public abstract Type BaseCLRType { get; }
public override Type BaseCLRType
{
get
{
return typeof(TestClassBase);//返回你要继承的基类类型
}
}
3. 实现public abstract object CreateCLRInstance(Enviorment.AppDomain appdomain, ILTypeInstance instance);
public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
return new Adaptor(appdomain, instance);//创建一个新的实例
}
Adaptor为下面要创建的类,是实际的适配器类
4.实现public abstract Type AdaptorType { get; }
public override Type AdaptorType
{
get
{
return typeof(Adaptor);//这是实际的适配器类
}
}
5.创建实际的适配器类Adaptor需要继承你想继承的那个类,并且实现CrossBindingAdaptorType接口
①.实现CrossBindingAdaptorType接口
ILTypeInstance instance;
public ILTypeInstance ILInstance { get { return instance; } }
初始化 :
public Adaptor()
{
}
public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
this.appdomain = appdomain;
this.instance = instance;
}
其实我这里有点奇怪,因为注册继承适配器哪里并没有调用CreateCLRInstance,也就没有调用到Adaptor的含参构造函数,而是在ILTypeInstance中有调用,不过应该是内部的机制吧,这里不深究
②.实现基类中的属性,函数(将控制权转到脚本里去)
要注意,对于虚函数而言,必须设定一个标识位来确定是否当前已经在调用中,否则如果脚本类中调用base.Value就会造成无限循环,最终导致爆栈,属性的Get方法也属于虚函数,写法如下 :
IMethod mGetValue;
bool mGetValueGot;
bool isGetValueInvoking = false;
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;
}
}
③.不要忘记tostring()实现
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;
}
四.补充下CLR中错误提醒
1.注册委托适配器
Cannot find Delegate Adapter for:HotFix_Project.TestDelegate.Method(Int32 a), Please add following code:
appdomain.DelegateManager.RegisterMethodDelegate<System.Int32>();
2.没有为其他的委托类型写转换器(转成Func / Action)
Cannot find convertor for TestDelegateMethod
Please add following code:
appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>((act) =>
{
return new TestDelegateMethod((a) =>
{
((Action<System.Int32>)act)(a);
});
});
3.跨域继承未注册适配器
System.TypeLoadException: Cannot find Adaptor for:TestClassBase