代码分支: feature/Hotfix_test_RonMacPro_180823
我的测试分支的源码里,直接搜索
RonILRuntime
就能快速找到对应的源码
然后,遇到一些奇怪的问题.我都总结在最后面.这个大家要注意一下
1, 首先要做的注意事项目
你需要将下列源码目录复制Unity工程的Assets目录:
Mono.Cecil.20
Mono.Cecil.Pdb
ILRuntime
2, 如果是UnityEngine.dll 引用不正确,则需要手动指引一下
3, 如果要调试,则需要安装对应的插件(目测只有windows下才有)
4, 为什么需要向ILRuntime注册委托?
不一定对. 由于C#跟C++交互(底层会通过IL2CPP转成C++),而C#的委托是一个对象类型,C++那边无法直接识别,所以要先向
ILRuntime注册这个对象的桥梁,让运行时,C++能正确识别
注意事项:
A, 同一种类型的注册一次即可
delegate void SomeDelegate(int a, float b);
Action<int, float> act;
上面2个只需要注册一个即可
appDomain.DelegateManager.RegisterMethodDelegate<int, float>();
B, 如果是带返回类型的委托,例如:
delegate bool SomeFunction(int a, float b);
Func<int, float, bool> act;
则这样注册
appDomain.DelegateManager.RegisterFunctionDelegate<int, float, bool>();
C, Action和Func的区别在于
Action就是定义一个没有返回的委托
而Func定义是一个有返回值的委托
D, 如果是自己的函数,没有采用Func和Action的,则需要自己额外手动写
例如我们在Hotfix.cs里面写的FairyGUI的委托.当出现自己的委托没有注册这个的时候.ILRuntime会自动提示你的
核心原理是 它要帮你转成 Action/Func 这样的类别来做桥接(我的理解)
this.appDomain.DelegateManager.RegisterDelegateConvertor<EventCallback0>((act) =>
{
return new EventCallback0(() =>
{
((Action)act)();
});
});
E, 总结以上的, 官方给出建议,尽量使用Func/Action这2个万能的委托. 并且尽量不要做跨域委托调用
F, 关于语法糖的委托测试. 在feature/Hotfix_test_RonMacPro_180823 分支下的 init.cs
// 我自己的测试没有参数的
mc.UseDelegateNoneArg( () => {
this.TestRonFuc();
}
);
// 测试1个参数的
mc.UseDelegate(s =>
{
Log.Debug(s);
}, "Hello!”);
// 测试2个参数的
mc.UseDelegateTwoArg( (a, b) =>
{
this.TestRonFucTwoArg(a, b);
}, "Hello Two Arg", 0
);
这里一个参数的(), s=>, (a, b) => 都只是对应参数个数而已
==================第二章-跨域继承==================分割线==================
1, 如果dll中要继承主项目的类, 或者实现主工程的一个接口, 则需要做跨域继承(跨域绑定).
详情可以参考 feature/Hotfix_test_RonMacPro_180823 分支
文件 ILRonTest_ ClassInheritanceTest_Adaptor.cs
在这里一点要千万注意的, 如果你的要继承某个函数,一定要用关键字override, 而非virtual, 如果用virtual
则会进2次这个函数, 找了很久, 但目前还尚未得知ILRuntime为什么这样
另外有必要说一点,当你运行时,dll里面(hotfix层的),全都包在ILRuntime里面,相当于是这样一个形态
dll里所有东西,都包在ILRuntime里面, 然后Dll <—> ILRuntime <—> Unity 这样的一个交互方式.
在我的理解中,
2, 尽量不要一个Adapter实现多个跨域继承.
如果是单个跨域继承的.则应该实现好这个接口(具体的代码可以看我的例子)
public override Type BaseCLRType
如果是一个Adapter要跨域继承多个的,则应该实现这个接口(具体代码看例子)
public override Type[] BaseCLRTypes
2个接口照着写就行了
public override Type AdaptorType
public override objectCreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain,ILTypeInstance instance)
3, 这一步是要实现类Adaptor, 我看例子是直接把这个类嵌套在ClassInheritanceAdaptor
class Adaptor : ClassInheritanceTest, CrossBindingAdaptorType
这个类基本copy就行了.
然后实现你要跨域的接口. 例如我例子中的这2个接口
public override void TestAbstract()
public override void TestVirtual(string a)
4, 当你把Adaptor做好之后, 就要在主工程里面做一个跨域继承的注册
Game.Hotfix.LoadHotfixAssembly();
#if ILRuntime
Game.Hotfix.appDomain.RegisterCrossBindingAdaptor(newClassInheritanceAdaptor());
#endif
# 主工程里面这样用
ClassInheritanceTest obj = Game.Hotfix.appDomain.Instantiate<ClassInheritanceTest>("ETHotfix.RonDerivedClass");
obj.TestAbstract();
obj.TestVirtual("Create 1111”);
# dll里面这样用
RonDerivedClass obj = new RonDerivedClass();
obj.TestAbstract();
obj.TestVirtual("Hello world!");
===================第三章-ILRuntime中的反射--分割线===================
1, 根据上面的ILRuntime和主工程的交互,主工程是无法识别ILRuntime里的东西,那么通过反射就能调用了(我之前基本没接触过反射这概念,遇到这个后,比较好理解了).
A, 在主工程中,得到dll中的实例,并且通过反射调用他的某个方法
B, 并且在主工程里,通过FieldInfo的方式填充BPGameData实例的PlayTime属性.
// RonILRuntime 测试主工程获取dll中的对象
IMethod getBPGameDataMethod =Game.Hotfix.appDomain.GetType("ETHotfix.Init").GetMethod("GetBpGameData", 0);
object bpGameDataObj =Game.Hotfix.appDomain.Invoke(getBPGameDataMethod, null, null);
Log.Debug("主工程层获取bpGameData ========>" +bpGameDataObj.GetHashCode());
// 然后调用这个对象的方法 SaveGameData
IType dataType =Game.Hotfix.appDomain.LoadedTypes["ETHotfix.BPGameData"];
Type dataT = dataType.ReflectionType;
MethodInfo SaveGameDataFunc = dataT.GetMethod("SaveGameData", 0);
SaveGameDataFunc.Invoke(bpGameDataObj, null);
// // RonILRuntime 测试主工程获取dll中的对象 + 调用它带参数的某个方法
IMethod getBPGameDataMethod =Game.Hotfix.appDomain.GetType("ETHotfix.Init").GetMethod("GetBpGameData", 0);
object bpGameDataObj =Game.Hotfix.appDomain.Invoke(getBPGameDataMethod, null, null);
IType dataType =Game.Hotfix.appDomain.LoadedTypes["ETHotfix.BPGameData"];
Type dataT = dataType.ReflectionType;
IMethod imObj = dataType.GetMethod("SaveGameData", 1);
Game.Hotfix.appDomain.Invoke(imObj, bpGameDataObj, 5);
// 用反射设置instance中的值
FieldInfo playTimeFieldInfo = dataT.GetField("playTime");
object val = playTimeFieldInfo.GetValue(bpGameDataObj);
Log.Debug("得到dll中的bpGameData PlayTime的值 ======>" + val);
playTimeFieldInfo.SetValue(bpGameDataObj, 7777);
val = playTimeFieldInfo.GetValue(bpGameDataObj);
Log.Debug("设置后的属性值 PlayTime的值 2222 ======>" + val);
2, 在主工程中,如果要创建dll中的对象,然后在调用它的某个方法. 那么应该这么做
// A, 主工程创建dll中的对象 + 调用它的某个方法
IType bpGameDataIType =Game.Hotfix.appDomain.LoadedTypes["ETHotfix.BPGameData"];
Type bpGameDataType = bpGameDataIType.ReflectionType;
Log.Debug("主工程调用dll的 创建对象之前..........");
ILTypeInstance bpGameDataObj =Game.Hotfix.appDomain.Instantiate("ETHotfix.BPGameData");
MethodInfo mi = bpGameDataType.GetMethod("SaveGameData", 0);
mi.Invoke(bpGameDataObj.CLRInstance, null);
// B, 主工程创建dll中的对象 + 调用它的某个方法(带参数的)
IType bpGameDataIType =Game.Hotfix.appDomain.LoadedTypes["ETHotfix.BPGameData"];
Type bpGameDataType = bpGameDataIType.ReflectionType;
ILTypeInstance bpGameDataObj =Game.Hotfix.appDomain.Instantiate("ETHotfix.BPGameData");
IMethod im = bpGameDataIType.GetMethod("SaveGameData", 1);
Game.Hotfix.appDomain.Invoke(im, bpGameDataObj.CLRInstance, 5);
3, 在dll中, 实例主工程中的某个类型.然后在调用
// RonILRuntime 在这里用反射创建主工程的东西. 然后直接调用(这个是不带参数的)
Type myClassT = Type.GetType("ETModel.MyClass");
object newObj = Activator.CreateInstance(myClassT);
MyClass myClassObj = (MyClass)newObj;
myClassObj.Method("WirteLog”);
// 带参数的(其实dll中调用主工程中的,是可以不用通过反射的.
我问了ILR的技术人员,他的意思是ILR在哪个时候,能得到主工程里的信息.所以可以直接这样调用
myClassObj.WirteLogWithArg("不用通过反射直接调用");
4, 获取主工程的中某个对象
# 直接在hotfix工程里面定义一个这样的
public ETModel.Scene ModelScene;
Game.Scene.ModelScene = ETModel.Game.Scene;
为什么可以这样做, 是因为ILR已经识别主工程的东西,所以能直接赋值到dll里.
5, 限制: 在Unity主工程中不能通过new T()的方式来创建热更工程中的类型实例
这个是需要通过CLR重定向来做(因为反射没办法做,为什么? 我还没想明白).
因为那个时候,主工程无法直接dll中的类型,所以不能用new T(). 得通过反射来创建
类似这样
IObject obj1=(IObject)Activator.CreateInstance(System.Type.GetType ("ActivatorCreateInstance.ClassExam"));
6, 暂时不知道怎么获取静态对象
===================第四章-CLR--分割线===================
CLR重定向
重定向的目的是为了有一些无法直接通过反射来实现的东西,就需要这个了(譬如 new T()),
它的原理是ILR挟持了这个方法,当你调用的时候,它会转到去你注册的那个方法里.
下面以UnityEngine.Debug.Log()为例子
在ILRuntimeCLRBinding.cs里有一个GenerateCLRBinding函数
里面就有一行代码是
types.Add(typeof(Debug));
做CLR的2个目的是:
1, 为了防止在iOS被剪裁
2, 可以少用反射,和GC Alloc. 这样能提高效率
不过这里要注意的是,真正的在项目里做CLR的时候.不是这样的.
他的原理是加载Dll, 然后看dll中用到了那些函数. 譬如UnityEngine.Debug.Log这样的函数. 他就会去做CLR