转自:https://www.cnblogs.com/gangtie/p/13665727.html
打标签策略:(4条消息) Unity XLua 配置 标签_王王王渣渣的博客-CSDN博客
Test类打上[HotFix]标签后,执行XLua/Generate Code后,xLua会根据内置的模板代码生成器在XLua目录下的Gen目录中生成一个DelegatesGensBridge.cs文件,该文件在XLua命名空间下生成一个DelegateBridge类,这个类中的__Gen_Delegate_Imp*函数会映射到xlua.hotfix中的function。代码如下:
1
|
namespace XLua
|
生成适配器代码后,执行XLua/Hotfix inject in Editor后,xLua会使用Mono.Cecil库对当前工程下的Assembly-CSharp.dll程序集进行IL注入。IL是.NET平台上的C#、F#等高级语言编译后产生的中间代码,该中间代码IL再经.NET平台中的CLR(类似于JVM)编译成机器码让CPU执行相关指令。由于移动平台无法把C#代码编译成IL中间代码,所以绝大多数热更新方案都会涉及到IL注入,只有这样Unity内置的VM才能对热更新的代码进行处理。下面是Unity使用Mono VM的脚本编译执行过程:
Mono是社区对.NET Framework的跨平台实现方案,实现了.NET Framework的绝大部分类库,因此基于Mono研发的Unity引擎才具有跨平台能力。而Mono VM就是基于Mono框架实现的,不同的平台实现不同的Mono VM,从而可以不同平台上执行C#脚本。由于IL代码是C#代码编译而来的,因此我们可以借用ILSpy工具对C#编译出来的程序集DLL文件进行反编译得到C#源代码,看看IL注入后打上[HotFix]标签的类的变化。注入后的C#代码如下:
1
|
[Hotfix(HotfixFlag.Stateless)]
|
从反编译的C#代码看出,xLua进行IL注入时会为打上[Hotfix]标签的类的所有函数创建一个DelegateBridge变量,同时添加对应的判断条件。如果Lua脚本中添加了对应的热更新函数,DelegateBridge变量就不为空,并将DelegateBridge变量中的__Gen_Delegate_Imp0方法指向xlua.hotfix(CS.XXX, “Start”, function(self))中的具体function。这时由于DelegateBridge变量不为空,所以C#中的函数就会执行Lua脚本中对应的热更新函数逻辑。但如果没有定义对应的热更新函数,或对应的热更新函数为nil,DelegateBridge变量就为空,则C#中的函数依然执行原有的函数逻辑。因此,xLua热更新实际上就是在运行时用Lua函数替换对应的C#函数。
与xLua热更新相关的标签还包括:[LuaCallCSharp]、[ReflectionUse]和[CSharpCallLua],这三个标签都需要生成适配代码,但不需要IL注入。[LuaCallCSharp]标签表示如果一个C#类型添加了该标签,xLua会生成这个类型的适配代码(包括构造该类型实例,访问其成员属性、方法,静态属性、方法),否则将会尝试用性能较低的反射方式来访问。比如,Lua脚本中想调用某个C#函数,就可以在该C#函数上添加[LuaCallCSharp]标签,这时Lua就会去寻找该函数的适配代码,然后进行调用。如果没有添加该标签,xLua就会尝试用反射的方式进行调用,但性能低于适配代码,而且在IL2CPP下还有可能因为代码剪裁而导致无法访问。IL2CPP是Unity推出的用来替代Mono VM的编译器,IL2CPP的脚本编译过程如下:
从上图看到,IL2CPP实际上是将C#编译得到的IL代码转换成C++代码,然后再由各个平台的原生C++编译器将C++代码编译成原生汇编代码(ASM汇编指令)。虽然代码转换成了C++代码,但我们知道C#中的内存是由GC自动管理,而C++需要手动管理内存,因此还需要一个IL2CPP VM用于GC管理等操作。IL2CPP的优点性能得到提升,运行速度更快,其次是编译成C++后反编译更难,进而安全性更高。缺点就是IL2CPP打包速度慢,而且转换后的C++代码量猛增,进而可能超过iOS平台可执行文件大小的限制。从2019年8月开始,Google Play上架的APP必须支持64位,因此只能发布时只能采用IL2CPP了,但平时开发调试时还是可以采用Mono,因为Mono出包快。
要想解决这个问题就要对UnityEngine下的代码进行Strip裁剪,但这容易导致反射时找不到对应的类型。因为Unity在程序运行前会对代码中没用引用到的地方进行裁剪,而反射必须在程序运行时才能确定要引用的类,如果进行裁剪可能会导致程序运行时通过反射找不到对应的类或函数,从而报错。唯一的解决方法就是在Assets目录下新建一个名为link.xml的XML文件,告诉Unity哪些类型不能被裁剪。[ReflectionUse]标签就是表示如果一个类打上该标签,xLua就把该类型添加进link.xml以阻止il2cpp的代码剪裁。因此,要想在各个平台上都能通过Lua访问到C#的类型,就必须在C#类型上添加[LuaCallCSharp]或[ReflectionUse]标签。
[CSharpCallLua]标签,表示如果C#想要访问Lua中函数或Table,就要在C#中对应的Delegate或Interface添加该标签。尽管还有其他映射方式,但最好通过Delegate来映射Lua中的函数,通过Interface来映射Lua中的Table。
在实际开发时,这些标签可以通过自定义配置来自动添加,配置文件放在XLua目录下的Editor文件夹中,下面是具体的配置建议:
1)、游戏刚上线不确定哪些类需要添加[Hotfix]标签时,可以使用反射把当前程序集下的所有类型都加上[Hotfix]标签,还可以设置条件进行过滤。
2)、用反射找出所有函数参数、字段、属性、事件涉及的delegate类型,标上[CSharpCallLua]用于C#映射Lua中的函数。
3)、业务代码、引擎API、系统API等需要在Lua里高性能访问的类型,标上[LuaCallCSharp],这样就Lua就会从生成的适配代码里找从而性能更高,不然Lua会尝试用反射的获取对应的类型,这会产生大量的性能消耗。
4)、引擎API、系统API在IL2CPP下可能被代码剪裁(C#无引用的地方都会被剪裁),这样Lua采用反射的方式获取对应的类型时就会出错。因此,如果觉得可能会新增C#代码之外的API调用,那么这些API所在的类型就必须添加[LuaCallCSharp]或[ReflectionUse]标签。