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进行注册

相关推荐
2401_894915535 小时前
GEO 定位优化源码搭建常见报错排查:数据库、伪静态、接口调试
java·数据库·网络协议·tcp/ip·spring·unity
xcLeigh1 天前
Unity基础:C#脚本的创建与挂载——让你的游戏物体活起来
游戏·unity·c#·教程
DaLiangChen2 天前
Vuforia 地面识别案例
unity·ar
江畔柳前堤2 天前
YOLO 目标检测全流程深度剖析
人工智能·yolo·目标检测·计算机视觉·unity·面试·vllm
魔术师Dix2 天前
一个轮子:Luban Skill 制作演示
游戏·unity·ai编程
xcLeigh3 天前
Unity基础:Light灯光组件入门——Directional Light、Point Light与Spotlight
unity·游戏引擎·教程
郝学胜-神的一滴3 天前
[简化版 GAMES 104] 现代游戏引擎 04:从0到1构建你的游戏世界
c++·游戏·unity·游戏引擎·软件工程·unreal engine