轉載請標明出處:http://www.cnblogs.com/zblade/
前面兩篇文章從頭到尾講解了C#熱更新的一些方案,從程序域來加載和卸載DLL,到使用ILRuntime來實現安卓和IOS平台的DLL熱更新。文章二中講解了ILRuntime對於IL虛擬機在加載DLL的過程中的一些解構。那么今天收尾的文章,就來講解一下如何基於這個虛擬機實現對於類,方法,屬性的調用。
一、基於appDomain來加載類實現反射的調用
對於ILRuntime中的反射,大概可以分為三種類型:一種是ILRuntime運行的DLL中,該程序集中的類之間的反射,這是基於C#的反射,應用無差別; 一種是unity游戲主工程中對DLL中類的反射,這樣的反射可以分為兩種,一種是獲取類,然后直接調用方法, 一種是基於appDomain包裝的反射,下面分別舉例兩種unity主工程對DLL中類的反射,來研究一下如何實現這個過程。
1、基於LoadedTypes來實現反射方法的調用
在ILRuntime中,不能基於System.Type來直接獲取熱更新DLL中的類,只有基於唯一的appDomain實例,基於LoadedTypes這種來獲取熱更新中的DLL,基於代碼來分析,更為詳細:
首先,加載獲取該DLL中的指定類:
var it = appDomain.LoadedTypes["HotFix_Project.InstanceClass"]
跟蹤LoadedTypes:
public Dictionary<string, IType> LoadedTypes{get{return mapType.InnerDictionary;}}
跟蹤看mapType.InnerDictionary:
ThreadSafeDictionary<string, IType> mapType = new ThreadSafeDictionary<string, IType>();
這個mapType是什么時候裝配的?
來自於文章二中的LoadAssembly的后續操作:
那么這個module.GetTypes是如何操作的?
分別基於協程來return type以及其nestedTypes,關鍵是看Types是怎么獲取的:
關鍵是read操作:
繼續跟進Read操作:
關鍵是:
var mtypes = metadata.Types
后續都是對其的封裝和填充,對於metadata的填充,來自於InitializeTypeDefinitions這個操作:
關鍵操作是ReadType這個操作:
構建一個內部定義的類,然后做數據填充,看看關鍵的幾個屬性的設置:BaseType ,設置其父類型,fieldsrange/methods_range 是對屬性范圍和方法范圍的設置:
所以基本方法還是ReadListRange:
在這兒,我們最終回到了文章二中對於IL虛擬機中的tableHeap的引用,最后實現了和文章二的首尾呼應。
好了,收起思緒,回到最開始的,獲取類,這樣獲得的一個類,這樣得到的一個類,繼承自IType,在Unity主工程中,則需要System.Type才能繼續使用反射接口,其對於的封裝來自昱這個ILType封裝的ReflectionType, 其中的ILRuntimeType繼承自Type類:
基於其,可以直接調用System.Type的GetConstructor方法,構建實例,歸並幾個代碼,可以表示為(直接使用的實例源代碼):
var it = appDomain.LoadedTypes["HotFix_Project.InstanceClass"]; var type = it.ReflectionType; var ctor = type.GetConstructor(new System.Type[0]); var obj = ctor.Invoke(null);
對應可以得到DLL中該類的構造函數的調用:
2、基於appDomain內嵌的Invoke來實現反射
在ILRuntime中,在appDomain中內嵌了一套Invoke的實現,可以在Unity工程中直接調用來實現對熱更新DLL中類的方法的調用:
關鍵操作就是2步: GetType和 GetMethod,獲取類型的過程,和前面有點類似,就是對mapType中存儲的獲取,如果沒有,則進行查找和填充,這兒重點說說方法是如何獲取的:
粗看就是從methods中取出來,做相應的檢查,如果通過則返回,那么初始化操作看看:
最后還是從definition.Methods中取出,逐個遍歷其中的方法做一個分類存儲,如果有靜態構造函數,且滿足對於的參數條件,則執行一次靜態構造。
回到開始,在獲取到類和方法的相關信息后,就可以執行對於的參數檢驗,然后執行反射:
可見,就是獲取到一個IL的解釋器,然后執行相應的反射,具體Run怎么執行,就不繼續深入貼圖了,有興趣的可以持續跟蹤(基本思路就是對stack的操作,塞入各個參數,然后執行一次操作,塞入結果,然后退回)
對於ILRuntime的反射基本就先研究到這兒,如果要應用到自己的項目中,可以繼續深入研究一下代碼,看看實現的具體細節。這兒附上開源的相關文檔:
二、熱更新DLL和Unity主工程的相互調用
基於前面的反射,我們可以基本理出熱更DLL和unity主工程的交互本質: 基於IL虛擬機或者.net本身反射來實現交互,對於熱更新DLL,其調用unity主工程,則主要是在熱更新工程中添加對於unity工程的Assembly-CSharp的引用:
基於這個引用,可以調用其中類的各自方法,舉兩個類來測試:
一個不繼承自MonoBehaviour:
一個繼承自MonoBehaviour:
這兩個Unity主工程中的類以及其中的方法,在熱更新DLL中調用:
可以在Unity主工程中得到輸出:
看一下track可以大概了解整個反射的執行過程。
對於Unity執行熱更DLL中的調用,就是第一部分的反射實例。
總結:絮絮叨叨的寫了三篇文章,算是對最近的研究做一個總結吧,現在項目還在評估這種熱更新方案,基於穩妥,以及有基於slua的熱更新項目用過,ILRuntime還在評估,其實本質都是相通的:基於自我實現的虛擬機(lua的虛擬機或者IL虛擬機,均基於棧實現),來構建一個自我運行環境,在里面解析執行對於的指令(lua虛擬機的指令/IL語句),來實現對熱更新的代碼(雖然是以資源方式熱更新)。