u3d插件xLua[四]Lua访问C#源码分析,反射,生成代码

本文将致力于研究xLua中Lua访问C#源码分析,反射,生成代码的用法

要理解xLua的源码,其实挺难的,需要的必备知识比较多:C#、Lua、反射、LuaC API

一.前言

从官网faq中的信息可以得出lua和C#的交互技术有生成代码和反射两种方式,官方没有对此作详细的解释。本文致力于从源码上分析梳理展开这部分技术。

参考:faq.md

回顾u3d插件xLua一环境搭建和Tutorial分析

来看这个demo,C#侧的类DerivedClass可以在Lua中当成table来使用:CS.Tutorial.DerivedClass。这个实现不是理所当然的,有大量代码在对其进行实现,下文将探索幕后的内容。

二.Demo

来看一个Demo,所有脚本改动的内容都有对比,见下图。

Demo展示的内容是:通过DoString执行Lua代码时,lua中访问C#时(比如下面的local a,local b local c),是执行了C#代码的

cs 复制代码
	local a = CS.UnityEngine.Vector3
	local b = CS.UnityEngine.Vector3.up
	local c = CS.UnityEngine.Time.deltaTime

打开场景U3DScripting

LuaTestScript.lua.txt

LuaBehaviour.cs

LuaEnv.cs

ObjectTranslator.cs

运行打印:

在Console中的打印可以看出,在lua中首次访问类时(比如Vector3),会先走LuaEnv中的元方法:

Lua 复制代码
            function metatable:__index(key) 
                print('***###', key)
                local fqn = rawget(self,'.fqn')
                fqn = ((fqn and fqn .. '.') or '') .. key

然后从这行进入C#

Lua 复制代码
local obj = import_type(fqn)

一直执行到ObjectTranslator.TryDelayWrapLoader,这个函数是本文的核心

cs 复制代码
public bool TryDelayWrapLoader(RealStatePtr L, Type type)

阅读这个方法,可以得出其通过3种方式将C#类本身和类的成员注册到Lua,后文将做进一步分析(知道大致逻辑即可,无需精细研究)

2.1.走CodeEmit类

cs 复制代码
                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(反射)

cs 复制代码
                    UnityEngine.Debug.Log("TryDelayWrapLoader: " + type + " -> ReflectionWrap");
                    Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));

2.3.走生成代码

cs 复制代码
            if (delayWrap.TryGetValue(type, out loader))
            {
                // 有生成代码:走 XxxWrap.__Register
                UnityEngine.Debug.Log("TryDelayWrapLoader: " + type + " -> GenWrap.__Register");
                delayWrap.Remove(type);
                loader(L);
            }

三.Lua访问C#方式一:走CodeEmit类

cs 复制代码
                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的掌握,不然看不懂。这里先不展开,等用到了再深入研究。

cs 复制代码
public Type EmitTypeWrap(Type toBeWrap)

参考:生成动态方法和程序集

四.Lua访问C#方式二:走 Utils.ReflectionWrap(反射)

cs 复制代码
Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
cs 复制代码
public static void ReflectionWrap(RealStatePtr L, Type type, bool privateAccessible)
cs 复制代码
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对栈顶附近的数据布局有严格的要求:

  1. 表 (Table):位于栈上的有效索引 index 处。

  2. 键 (Key):位于栈顶下方(-2 位置)。

  3. 值 (Value):位于栈顶(-1 位置)。

五.Lua访问C#方式三:走生成代码

5.1.收集

通过GetGenConfig,扫一遍当前域里几乎所有类型,把配置合并进几个静态列表

在MergeCfg中会收集特性:LuaCallCSharpAttribute

5.2.生成

由Generator.GenOne完成写入文件,由genCode保存文件文本内容,下图是为Vector2类生成UnityEngine_Vector2Wrap.cs的过程

5.3.运行

第一次Lua访问C#类中成员时,由Wrap文件调用RegisterFunc注册,来看一个例子:

LuaTestScript.lua.txt

Lua 复制代码
function start()
	print("lua start...")
	local d = CS.XLuaTest.NoGc.a1
end

从调用堆栈可以看出调用链为:LuaBehaviour.Start->LuaTestScript.Start->...->XLuaTestNoGcWrap.__Register->Utils.RegisterFunc进行注册

相关推荐
Martina_03213 天前
AI生成3D场景导入 Unity/Unreal 后,NPC 总串层?用6步检查导航高度与跨层连接
人工智能·游戏·数学建模·3d·unity·自然语言处理·aigc
小贺儿开发3 天前
Unity 局域网遥控幻灯片展示工具 3.0
科技·http·unity·工具·ppt·通信·网页
WarPigs5 天前
Unity对象不应该使用null传播的问题
unity
每日出拳老爷子6 天前
【VR】【Unity】抓走转放系列21|一天搭完门和抽屉工具槽:小场景按这个次序拼装
unity·vr·驱动·排错·snap zone
淡海水7 天前
02-02-原理篇-分代GC
算法·unity·c#·游戏引擎·.net·gc
小贺儿开发7 天前
Unity 文物新生 会讲故事的文物展墙
人工智能·科技·unity·ai·视频·互动·演示
淡海水7 天前
02-03-原理篇-大对象堆
unity·c#·游戏引擎·.net·gc
WarPigs8 天前
Unity在Create菜单添加创建自定义脚本模板选项
unity
淡海水8 天前
02-01-原理篇-Mark-Sweep与变种算法
算法·unity·c#·游戏引擎·.net·gc
babywang08 天前
Unity笔记--塔防游戏中的ECS架构:从原理到实战的全面解析
unity·ecs架构