Unity 游戏用XLua的HotFix实现热更原理揭秘
本文通過對XLua的HoxFix使用原理的研究揭示出來這樣的一套方法。這個方法的第一步:通過對C#的類與函數(shù)設置Hotfix標簽。來標識需要支持熱更的類和函數(shù)。第二步:生成函數(shù)連接器來連接LUA腳本與C#函數(shù)。第三步:在C#腳本編譯結束后,使用Mono提供的一套C#的API函數(shù),對已經(jīng)編譯過的.Net體系生成的DLL文件進行修改。第四步,通過LUA腳本修改C#帶有標簽的類中靜態(tài)變量,把代碼的執(zhí)行路徑修改到LUA腳本中。通過這套方案可以實現(xiàn)對已經(jīng)標識的C#代碼進行動態(tài)更新。
基礎準備
知識準備
CIL: 通用中間語言(Common Intermediate Language,簡稱CIL), 是一種屬于通用語言架構和 .NET 框架的低階(lowest-level)的人類可讀的編程語言。目標為 .NET 框架的語言被編譯成CIL(基于.NET框架下的偽匯編語言,原:MSIL),這是一組可以有效地轉換為本機代碼且獨立于 CPU 的指令。CIL類似一個面向對象的匯編語言,并且它是完全基于堆棧的。它運行在CLR上(類似于JVM),其主要支持的語言有C#、VisualBasic .NET、C++/CLI以及 J#(集成這些語言向CIL的編譯功能)。
在編譯.NET編程語言時,源代碼被翻譯成CIL碼,而不是基于特定平臺或處理器的目標代碼。CIL是一種獨立于具體CPU和平臺的指令集,它可以在任何支持.NET framework的環(huán)境下運行。CIL碼在運行時被檢查并提供比二進制代碼更好的安全性和可靠性。在Unity3D中,是用過Mono虛擬機來實現(xiàn)運行這些中間語言指令的。
之前寫一篇介紹過一篇使用微軟的API函數(shù),利用中間語言生成或注入.NET支持下的DLL。這里就不在贅述,需要了解的請參考《使用MSIL采用Emit方式實現(xiàn)C#的代碼生成與注入》。
?
IL2CPP: 直接理解把IL中間語言轉換成CPP文件。根據(jù)官方的實驗數(shù)據(jù),換成IL2CPP以后,程序的運行效率有了1.5-2.0倍的提升。引用地址:http://blog.csdn.net/gz_huangzl/article/details/52486255
使用Mono的時候,腳本的編譯運行如下圖所示:
簡單的來說,3大腳本被編譯成IL,在游戲運行的時候,IL和項目里其他第三方兼容的DLL一起,放入Mono VM虛擬機,由虛擬機解析成機器碼,并且執(zhí)行
IL2CPP做的改變由下圖紅色部分標明:
在得到中間語言IL后,使用IL2CPP將他們重新變回C++代碼,然后再由各個平臺的C++編譯器直接編譯成能執(zhí)行的原生匯編代碼。
?
開啟IL2CPP的Unity構建流程:unity構建流程分步:
第一步:平臺資源處理,主要生成Library\metadata下面的文件。
第二步:腳本編譯(主要是C#腳本),Library\ScriptAssemblies下的Dll,主要是Assembly-CSharp.dll 和Assembly-CSharp-Editor.dll這兩個Dll。
第三步:把這個Assembly-CSharp.dll編譯成C++代碼。在IOS中,這里是導出Xcode的工程。Andriod中直接生成APK。
第四步:在IOS中,編譯Xcode,生成IPA。Andriod沒有這一步。
代碼注入方式
函數(shù)與屬性
?????? 首先需要加入Mono.Cecil庫 在Unity安裝目錄下 Editor\Data\Managed可以找到。
?????? 建立一個可以編輯AssemblyDefinition類。
AssemblyDefinitionassembiy = AssemblyDefinition.ReadAssembly(FileName);
獲取中間語言的類型
??? foreach (Mono.Cecil.TypeDefinitioniteminassembiy.MainModule.Types)
????? Console.Write("\nMainModule.Types" +? item.Name);
獲取指定類型的元素
item.Methods
獲取屬性的方法
item.Properties
注入代碼
????? 注入代碼的函數(shù)需要的參數(shù):
AssemblyDefinitionassembiy? 可以編輯的IL語言的定義集合。
MethodDefinitionmethod? 需要修改的函數(shù)方法
TypeDefinitionitem?? 需要修改的函數(shù)所屬于的類
Instructionins = method.Body.Instructions[0];?// 獲取指定函數(shù)的指令集合
ILProcessorkWorker = method.Body.GetILProcessor();?//獲取修改指令集
kWorker.InsertBefore(ins, kWorker.Create(OpCodes.Nop)); // 在固定指令之前加入代碼
?
ILProcessor函數(shù)介紹
Replace(Instructiontarget, Instructioninstruction); 指令替換
Append(Instructioninstruction);? 增加指令
InsertAfter(Instructiontarget, Instructioninstruction);在指令指令之前插入新指令
InsertAfter(Instructiontarget, Instructioninstruction);在指令指令之后插入新指令
?
Create(OpCodeopcode); //創(chuàng)建新指令
Emit(OpCodeopcode); // 默認注入在當前函數(shù)最后插入對象指令。
關于IL指令,請參考《使用MSIL采用Emit方式實現(xiàn)C#的代碼生成與注入》
工具準備
ILSpyversion:ILSpy是一個開源Net的瀏覽器和反編譯器。下載地址:http://ilspy.net/
使用指南:能把C#生成二進制文件轉換為MSIL 或者C# 任選一種,當想用Emit實現(xiàn)某一功能但是不知道怎么寫時,可以先把該功能的C#代碼寫出來,再用ILSpy將其轉換成MSIL,然后轉化為C#先查看是否能夠顯示出來。
測試環(huán)境準備
本節(jié)主要介紹如何能夠構成一個可以測試代碼注入的環(huán)境。
第一步:建立C#代碼代碼如下:
[Hotfix]
publicclassHotfixTest : MonoBehaviour {
??? LuaEnvluaenv = newLuaEnv();
??? publicinttick = 0; //如果是private的,在lua設置xlua.private_accessible(CS.HotfixTest)后即可訪問
??? //Update is called once per frame
??? voidUpdate () {
??? ??? if (++tick % 50 == 0){
??????????? Debug.Log(">>>>>>>>Updatein C#, tick = " + tick);
???????}
??? }
?
??? voidOnGUI() {
??????? if (GUI.Button(newRect(10, 100,300, 150), "Hotfix")){
??????????? luaenv.DoString(@"
???????????????xlua.hotfix(CS.HotfixTest, 'Update', function(self)
???????????????????self.tick = self.tick + 1
???????????????????if (self.tick % 50) == 0 then
???????????????????????print('<<<<<<<
???????self.tick = self.tick + 1
???????if (self.tick % 50) == 0 then
????????????print('<<<<<<<
??? for k, v in pairs(tbl) do
??????? local cflag = ''
??????? if k == '.ctor' then
???????????cflag = '_c'
???????????k = 'ctor'
??????? end
??????? local f = type(v) =='function' and v or nil
??????? xlua.access(cs, cflag.. '__Hitfix0_'..k, f) -- at least one
??????? pcall(function()
???????????for i = 1, 99 do
???????????????xlua.access(cs, '__Hitfix'..i..'_'..k, f)
???????????end
????? ??end)
??? end
end
通過Access修改
在Access對應的C#代碼是XLuaAccess代碼處理。下面主要處理函數(shù)修改的功能如下:
ObjectTranslatortranslator = ObjectTranslatorPool.Instance.Find(L);
object obj = translator.SafeGetCSObj(L, 1);
stringfieldName = LuaAPI.lua_tostring(L, 2);
varfield = type.GetField(fieldName, bindingFlags); //獲取函數(shù)名稱
field.SetValue(obj, translator.GetObject(L, 3, field.FieldType)); //修改當前函數(shù)名稱
修改創(chuàng)建函數(shù)對象
獲取函數(shù)功能:ObjectTranslator.CreateDelegateBridge
DelegateBridgeBaseexist_bridge = delegate_bridges[referenced].TargetasDelegateBridgeBase;
exist_delegate = exist_bridge.GetDelegateByType(delegateType);
?
通過類型來獲取函數(shù)的對象,如圖所示:
publicoverrideDelegateGetDelegateByType(Typetype){
??? if (type == typeof(__Gen_Hotfix_Delegate0)){
??????? returnnew__Gen_Hotfix_Delegate0(__Gen_Delegate_Imp1);
??? }
}
總結
?????? 本文主要揭示了Hoxfix的運行機制,便于能夠更好的使用Xlua的熱更機制。進一步了解C#的標簽、代碼生成、以及代碼注入的機制。這種機制能夠運行與Andriod和IOS平臺。同時也能對是否使用IL2CPP沒有影響。
原文地址:http://gad.qq.com/article/detail/7201590
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關注
總結
以上是生活随笔為你收集整理的Unity 游戏用XLua的HotFix实现热更原理揭秘的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Work Time Manager【开源
- 下一篇: WebAssembly,开发者赢了