ILRuntime技術相關的總結(轉)


官方地址
 
代碼分支: 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
       或者參考這里   https://www.jianshu.com/p/96526064ed38
        
            // 我自己的測試沒有參數的
                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上成功執行)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM