官方地址
代碼分支: 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
我github的最新ET版測試代碼(CLR分析的時候.依然會有Bug)
git@github.com:Ron3/ET.git
這條分支(已經在Mac上成功執行)