u3d插件xLua[四]Lua访问C#源码分析,反射,生成代码
本文将致力于研究xLua中Lua访问C#源码分析反射生成代码的用法要理解xLua的源码其实挺难的需要的必备知识比较多C#、Lua、反射、LuaC API一.前言从官网faq中的信息可以得出lua和C#的交互技术有生成代码和反射两种方式官方没有对此作详细的解释。本文致力于从源码上分析梳理展开这部分技术。参考faq.md参考xlua源码分析六 C#与lua的交互总结回顾u3d插件xLua[一]环境搭建和Tutorial分析来看这个demoC#侧的类DerivedClass可以在Lua中当成table来使用CS.Tutorial.DerivedClass。这个实现不是理所当然的有大量代码在对其进行实现下文将探索幕后的内容。二.Demo来看一个Demo所有脚本改动的内容都有对比见下图。Demo展示的内容是通过DoString执行Lua代码时lua中访问C#时(比如下面的local a,local b local c)是执行了C#代码的local a CS.UnityEngine.Vector3 local b CS.UnityEngine.Vector3.up local c CS.UnityEngine.Time.deltaTime打开场景U3DScriptingLuaTestScript.lua.txtLuaBehaviour.csLuaEnv.csObjectTranslator.cs运行打印在Console中的打印可以看出在lua中首次访问类时(比如Vector3)会先走LuaEnv中的元方法function metatable:__index(key) print(***###, key) local fqn rawget(self,.fqn) fqn ((fqn and fqn .. .) or ) .. key然后从这行进入C#local obj import_type(fqn)一直执行到ObjectTranslator.TryDelayWrapLoader这个函数是本文的核心public bool TryDelayWrapLoader(RealStatePtr L, Type type)阅读这个方法可以得出其通过3种方式将C#类本身和类的成员注册到Lua后文将做进一步分析(知道大致逻辑即可无需精细研究)2.1.走CodeEmit类if (!DelegateBridge.Gen_Flag !type.IsEnum() !typeof(Delegate).IsAssignableFrom(type) Utils.IsPublic(type)) { UnityEngine.Debug.Log(TryDelayWrapLoader: type - CodeEmit); Type wrap ce.EmitTypeWrap(type); MethodInfo method wrap.GetMethod(__Register, BindingFlags.Static | BindingFlags.Public); method.Invoke(null, new object[] { L }); }2.2.走Utils.ReflectionWrap(反射)UnityEngine.Debug.Log(TryDelayWrapLoader: type - ReflectionWrap); Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));2.3.走生成代码if (delayWrap.TryGetValue(type, out loader)) { // 有生成代码走 XxxWrap.__Register UnityEngine.Debug.Log(TryDelayWrapLoader: type - GenWrap.__Register); delayWrap.Remove(type); loader(L); }三.Lua访问C#方式一走CodeEmit类if (!DelegateBridge.Gen_Flag !type.IsEnum() !typeof(Delegate).IsAssignableFrom(type) Utils.IsPublic(type)) { Type wrap ce.EmitTypeWrap(type); MethodInfo method wrap.GetMethod(__Register, BindingFlags.Static | BindingFlags.Public); method.Invoke(null, new object[] { L }); }把这3行代码做的事放一起说在内存中生成*Wrap类然后将类中的成员挂到Lua的表/元表上从而支持Lua → C# 的调用。实现上主要在CodeEmit.EmitTypeWrap中其比较难于理解需要较多System.Reflection.Emit中API的掌握不然看不懂。这里先不展开等用到了再深入研究。public Type EmitTypeWrap(Type toBeWrap)参考生成动态方法和程序集四.Lua访问C#方式二走 Utils.ReflectionWrap(反射)Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));public static void ReflectionWrap(RealStatePtr L, Type type, bool privateAccessible)static void makeReflectionWrap(RealStatePtr L, Type type, int cls_field, int cls_getter, int cls_setter, int obj_field, int obj_getter, int obj_setter, int obj_meta, out LuaCSFunction item_getter, out LuaCSFunction item_setter, BindingFlags access)ReflectionWrap中通过反射将type的成员挂到lua侧类名这个表中核心实现在makeReflectionWrap中。通过调用LuaAPI.lua_rawset会执行类似t[k] v的操作然后将键和值都从栈中弹出lua_rawset对栈顶附近的数据布局有严格的要求表 (Table)位于栈上的有效索引index处。键 (Key)位于栈顶下方-2位置。值 (Value)位于栈顶-1位置。五.Lua访问C#方式三走生成代码5.1.收集通过GetGenConfig扫一遍当前域里几乎所有类型把配置合并进几个静态列表在MergeCfg中会收集特性LuaCallCSharpAttribute5.2.生成由Generator.GenOne完成写入文件由genCode保存文件文本内容下图是为Vector2类生成UnityEngine_Vector2Wrap.cs的过程5.3.运行第一次Lua访问C#类中成员时由Wrap文件调用RegisterFunc注册来看一个例子LuaTestScript.lua.txtfunction start() print(lua start...) local d CS.XLuaTest.NoGc.a1 end从调用堆栈可以看出调用链为LuaBehaviour.Start-LuaTestScript.Start-...-XLuaTestNoGcWrap.__Register-Utils.RegisterFunc进行注册