XLua 如何与 C# 进行交互

🔗 深入 xLua 实现原理之 Lua 如何调用 C#

xLua 是腾讯的一个开源项目,为 Unity、 .Net、 Mono 等 C# 环境增加 Lua 脚本编程的能力。本文主要是探讨 xLua 下 Lua 调用 C# 的实现原理

Lua 与 C# 数据通信机制

无论是 Lua 调用 C#,还是 C# 调用 Lua,都需要一个通信机制,来完成数据的传递。而 Lua 本身就是由 C 语言编写的,所以它出生自带一个和 C/C++ 的通信机制

Lua 和 C/C++ 的数据交互通过栈进行,操作数据时,首先将数据拷贝到”栈”上,然后获取数据,栈中的每个数据通过索引值进行定位,索引值为正时表示相对于栈底的偏移索引,索引值为负时表示相对于栈顶的偏移索引,索引值以 1 或 -1 为起始值,因此栈顶索引值永远为 -1, 栈底索引值永远为 1 。 “栈”相当于数据在 Lua 和 C/C++ 之间的中转地。每种数据都有相应的存取接口

而 C# 可以通过 P/Invoke 方式调用 Lua 的 dll,通过这个 dll 执行 Lua 的 C API。换言之 C# 可以借助 C/C++ 来与 Lua 进行数据通信。在 xLua 的 LuaDLL.cs 文件中可以找到许多 DllImport 修饰的数据入栈与获取的接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// LuaDLL.cs
[DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)]
public static extern void lua_pushnumber(IntPtr L, double number);

[DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)]
public static extern void lua_pushboolean(IntPtr L, bool value);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void xlua_pushinteger(IntPtr L, int value);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern double lua_tonumber(IntPtr L, int index);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int xlua_tointeger(IntPtr L, int index);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern uint xlua_touint(IntPtr L, int index);

[DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)]
public static extern bool lua_toboolean(IntPtr L, int index);

传递 C# 对象到 Lua

对于 boolint 这样简单的值类型可以直接通过 C API 传递。但对于 C# 对象就不同了,Lua 这边没有能与之对应的类型,因此传递到 Lua 的只是 C# 对象的一个索引,具体实现请看下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// ObjectTranslator.cs
public void Push(RealStatePtr L, object o)
{
// ...
int index = -1;
Type type = o.GetType();
#if !UNITY_WSA || UNITY_EDITOR
bool is_enum = type.IsEnum;
bool is_valuetype = type.IsValueType;
#else
bool is_enum = type.GetTypeInfo().IsEnum;
bool is_valuetype = type.GetTypeInfo().IsValueType;
#endif
bool needcache = !is_valuetype || is_enum; // 如果是引用或枚举,会进行缓存
if (needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index))) // 如果有缓存
{
if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1)
{
return;
}
// 这里实在太经典了,weaktable 先删除,然后 GC 会延迟调用,当 index 会循环利用的时候,不注释这行将会导致重复释放
//collectObject(index);
}

bool is_first;
int type_id = getTypeId(L, type, out is_first);

// 如果一个 type 的定义含本身静态 readonly 实例时,getTypeId 会 push 一个实例,这时候应该用这个实例
if (is_first && needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index)))
{
if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1)
{
return;
}
}
// C# 侧进行缓存
index = addObject(o, is_valuetype, is_enum);
// 将代表对象的索引 push 到 lua
LuaAPI.xlua_pushcsobj(L, index, type_id, needcache, cacheRef);
}

代码中的两个 if 语句主要是对缓存的判断,如果要传递的对象已经被缓存过了就直接使用缓存的。如果这个对象是被第一次传递,则进行以下两步操作

  1. 通过 addObject 将对象缓存在 objects 对象池中,并得到一个索引(通过这个索引可以获取到该对象)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    // ObjectTranslator.cs
    int addObject(object obj, bool is_valuetype, bool is_enum)
    {
    int index = objects.Add(obj);
    if (is_enum)
    {
    enumMap[obj] = index;
    }
    else if (!is_valuetype)
    {
    reverseMap[obj] = index;
    }

    return index;
    }
  2. 通过 xlua_pushcsobj 将代表对象的索引传递到 Lua

参数 key 表示代表对象的索引,参数 meta_ref 表示代表对象类型的表的索引,它的值是通过 getTypeId 函数获得的,后面会详细讲到。参数 need_cache 表示是否需要在 Lua 侧进行缓存,参数 cache_ref 表示 Lua 侧缓存表的索引

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// xlua.c
LUA_API void xlua_pushcsobj(lua_State *L, int key, int meta_ref, int need_cache, int cache_ref) {
int* pointer = (int*)lua_newuserdata(L, sizeof(int));
*pointer = key;

if (need_cache) cacheud(L, key, cache_ref); // Lua 侧缓存

lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref);

lua_setmetatable(L, -2); // 为 userdata 设置元表
}

// 将 key = userdata 存入缓存表
static void cacheud(lua_State *L, int key, int cache_ref) {
lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref);
lua_pushvalue(L, -2);
lua_rawseti(L, -2, key);
lua_pop(L, 1);
}

xlua_pushcsobj的主要逻辑是,代表对象的索引被 push 到 Lua 后,Lua 会为其创建一个 userdata,并将这个userdata 指向对象索引,如果需要缓存则将 userdata 保存到缓存表中, 最后为 userdata 设置了元表。也就是说,C# 对象在 Lua 这边对应的就是一个userdata,利用对象索引保持与 C# 对象的联系。

注册 C# 类型信息到 Lua

userdata(特指 C# 对象在 Lua 这边对应的代理userdata,后面再出现的userdata 也是同样的含义,就不再赘述了)设置的元表,表示的实际是对象的类型信息。在将 C# 对象传递到 Lua 以后,还需要告知 Lua 该对象的类型信息,比如对象类型有哪些成员方法,属性或是静态方法等。将这些都注册到 Lua 后,Lua 才能正确的调用。这个元表是通过 getTypeId 函数生成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
// ObjectTranslator.cs
internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN)
{
int type_id;
is_first = false;
if (!typeIdMap.TryGetValue(type, out type_id)) // no reference
{
// ...
is_first = true;
Type alias_type = null;
aliasCfg.TryGetValue(type, out alias_type);
LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);

if (LuaAPI.lua_isnil(L, -1)) //no meta yet, try to use reflection meta
{
LuaAPI.lua_pop(L, 1);

if (TryDelayWrapLoader(L, alias_type == null ? type : alias_type))
{
LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);
}
else
{
throw new Exception("Fatal: can not load metatable of type:" + type);
}
}

// 循环依赖,自身依赖自己的 class,比如有个自身类型的静态 readonly 对象。
if (typeIdMap.TryGetValue(type, out type_id))
{
LuaAPI.lua_pop(L, 1);
}
else
{
// ...
LuaAPI.lua_pushvalue(L, -1);
type_id = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX); // 将元表添加到注册表中
LuaAPI.lua_pushnumber(L, type_id);
LuaAPI.xlua_rawseti(L, -2, 1); // 元表 [1] = type_id
LuaAPI.lua_pop(L, 1);

if (type.IsValueType())
{
typeMap.Add(type_id, type);
}

typeIdMap.Add(type, type_id);
}
}
return type_id;
}

函数主要逻辑是以类的名称为 key 通过 luaL_getmetatable 获取类对应的元表,如果获取不到,则通过 TryDelayWrapLoader函数生成。然后调用 luaL_ref 将获取到的元表添加到 Lua 注册表中,并返回 type_id。type_id 表示的就是元表在 Lua 注册表中的索引,通过这个索引可以在 Lua 注册表中取回元表。前面提到的 xlua_pushcsobj函数就是利用 type_idmeta_ref,获取到元表,然后为 userdata 设置的元表。
下面来看元表具体是怎样生成的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// ObjectTranslator.cs
public bool TryDelayWrapLoader(RealStatePtr L, Type type)
{
// ...
LuaAPI.luaL_newmetatable(L, type.FullName); // 先建一个 metatable,因为加载过程可能会需要用到
LuaAPI.lua_pop(L, 1);

Action<RealStatePtr> loader;
int top = LuaAPI.lua_gettop(L);
if (delayWrap.TryGetValue(type, out loader)) // 如果有预先注册的类型元表生成器,则直接使用
{
delayWrap.Remove(type);
loader(L);
}
else
{
#if !GEN_CODE_MINIMIZE && !ENABLE_IL2CPP && (UNITY_EDITOR || XLUA_GENERAL) && !FORCE_REFLECTION && !NET_STANDARD_2_0
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 });
}
else
{
Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
}
#else
Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
#endif
// ...
}
if (top != LuaAPI.lua_gettop(L))
{
throw new Exception("top change, before:" + top + ", after:" + LuaAPI.lua_gettop(L));
}

foreach (var nested_type in type.GetNestedTypes(BindingFlags.Public))
{
if (nested_type.IsGenericTypeDefinition()) // 过滤泛型类型定义
{
continue;
}
GetTypeId(L, nested_type);
}

return true;
}

TryDelayWrapLoader 主要用来处理两种情况

  1. 通过 delayWrap 判断,是否有为该类生成代码,如果有,直接使用生成函数进行填充元表(loader方法)。在 xLua 的生成代码中有一个 XLuaGenAutoRegister.cs 文件,在这个文件中会为对应的类注册初始化器,而这个初始化器负责将类对应的元表生成函数添加到 delayWrap 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// XLuaGenAutoRegister.cs
public class XLua_Gen_Initer_Register__
{
static void wrapInit0(LuaEnv luaenv, ObjectTranslator translator)
{
// ...
translator.DelayWrapLoader(typeof(TestXLua), TestXLuaWrap.__Register); // 将类型对应的元表填充函数__Register 添加到 delayWrap 中
// ...
}

static void Init(LuaEnv luaenv, ObjectTranslator translator)
{
wrapInit0(luaenv, translator);
translator.AddInterfaceBridgeCreator(typeof(System.Collections.IEnumerator), SystemCollectionsIEnumeratorBridge.__Create);
}

static XLua_Gen_Initer_Register__()
{
XLua.LuaEnv.AddIniter(Init); // 注册初始化器
}
}
  1. 如果没有生成代码,通过反射填充元表(ReflectionWrap方法)

使用生成函数填充元表

LuaCallCSharp修饰的 TestXLua 类为例来查看生成函数是如何生成的

1
2
3
4
5
6
7
8
9
10
11
// TestXLua.cs
[LuaCallCSharp]
public class TestXLua
{
public string Name;
public void Test1(int a){
}
public static void Test2(int a, bool b, string c)
{
}
}

Generate Code 之后生成的 TestXLuaWrap.cs 如下所示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public class TestXLuaWrap 
{
public static void __Register(RealStatePtr L)
{
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
System.Type type = typeof(TestXLua);
Utils.BeginObjectRegister(type, L, translator, 0, 1, 1, 1);
Utils.RegisterFunc(L, Utils.METHOD_IDX, "Test1", _m_Test1);
Utils.RegisterFunc(L, Utils.GETTER_IDX, "Name", _g_get_Name);
Utils.RegisterFunc(L, Utils.SETTER_IDX, "Name", _s_set_Name);
Utils.EndObjectRegister(type, L, translator, null, null,
null, null, null);
Utils.BeginClassRegister(type, L, __CreateInstance, 2, 0, 0);
Utils.RegisterFunc(L, Utils.CLS_IDX, "Test2", _m_Test2_xlua_st_);
Utils.EndClassRegister(type, L, translator);
}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int __CreateInstance(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
if(LuaAPI.lua_gettop(L) == 1)
{
TestXLua gen_ret = new TestXLua();
translator.Push(L, gen_ret);
return 1;
}
}
catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
return LuaAPI.luaL_error(L, "invalid arguments to TestXLua constructor!");

}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_Test1(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
{
int _a = LuaAPI.xlua_tointeger(L, 2);
gen_to_be_invoked.Test1(_a);
return 0;
}
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_Test2_xlua_st_(RealStatePtr L)
{
try {
{
int _a = LuaAPI.xlua_tointeger(L, 1);
bool _b = LuaAPI.lua_toboolean(L, 2);
string _c = LuaAPI.lua_tostring(L, 3);
TestXLua.Test2(_a, _b, _c);
return 0;
}
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _g_get_Name(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);

TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
LuaAPI.lua_pushstring(L, gen_to_be_invoked.Name);
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
return 1;
}

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _s_set_Name(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);

TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
gen_to_be_invoked.Name = LuaAPI.lua_tostring(L, 2);

} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
return 0;
}
}

生成函数 __Register 主要是这样一个框架

  1. Utils.BeginObjectRegister,在对类的非静态值(例如成员变量,成员方法等)进行注册前做一些准备工作。主要是为元表添加 __gc__tostring元方法,以及准备好 method 表、getter表、setter表,后面调用 RegisterFunc 时,可以选择插入到对应的表中

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    // Utils.cs
    public static void BeginObjectRegister(Type type, RealStatePtr L, ObjectTranslator translator, int meta_count, int method_count, int getter_count,
    int setter_count, int type_id = -1)
    {
    if (type == null)
    {
    if (type_id == -1) throw new Exception("Fatal: must provide a type of type_id");
    LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, type_id);
    }
    else
    {
    LuaAPI.luaL_getmetatable(L, type.FullName);
    // 如果 type.FullName 对应的元表是空,则创建一个新的元表,并设置到注册表中
    if (LuaAPI.lua_isnil(L, -1))
    {
    LuaAPI.lua_pop(L, 1);
    LuaAPI.luaL_newmetatable(L, type.FullName);
    }
    }
    LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag());
    LuaAPI.lua_pushnumber(L, 1);
    LuaAPI.lua_rawset(L, -3); // 为元表设置标志

    if ((type == null || !translator.HasCustomOp(type)) && type != typeof(decimal))
    {
    LuaAPI.xlua_pushasciistring(L, "__gc");
    LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
    LuaAPI.lua_rawset(L, -3); // 为元表设置__gc 方法
    }

    LuaAPI.xlua_pushasciistring(L, "__tostring");
    LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);
    LuaAPI.lua_rawset(L, -3); // 为元表设置__tostring 方法

    if (method_count == 0)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    LuaAPI.lua_createtable(L, 0, method_count); // 创建 method 表
    }

    if (getter_count == 0)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    LuaAPI.lua_createtable(L, 0, getter_count); // 创建 getter 表
    }

    if (setter_count == 0)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    LuaAPI.lua_createtable(L, 0, setter_count); // 创建 setter 表
    }
    }
  2. 多个 Utils.RegisterFunc,将类的每个非静态值对应的包裹方法注册到不同的 Lua 表中。包裹方法是 Generate Code 时动态生成的,对于类的属性会生成两个包裹方法,分别是getset包裹方法

    例如成员方法 Test1 对应的包裹方法是 _m_Test1,并被注册到了method 表中。Name变量的 _g_get_Name 包裹方法被注册到getter 表,而 _s_set_Name 包裹方法被注册到 setter 表。这个包裹方法只是对原来方法的一层包裹,调用这个包裹方法本质上就是调用原来的方法。至于为什么需要生成包裹方法,后面会再讲到

    1
    2
    3
    4
    5
    6
    7
    8
    // Utils.cs RegisterFunc 根据不同的宏定义会有不同的版本,但大同小异
    public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func)
    {
    idx = abs_idx(LuaAPI.lua_gettop(L), idx);
    LuaAPI.xlua_pushasciistring(L, name);
    LuaAPI.lua_pushstdcallcfunction(L, func);
    LuaAPI.lua_rawset(L, idx); // 将 idx 指向的表中添加键值对 name = func
    }
  3. Utils.EndObjectRegister,结束对类的非静态值的注册。主要逻辑是为元表生成 __index 元方法和 __newindex 元方法,这也是 Lua 调用 C# 的核心所在

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    // Utils.cs
    public static void EndObjectRegister(Type type, RealStatePtr L, ObjectTranslator translator, LuaCSFunction csIndexer,
    LuaCSFunction csNewIndexer, Type base_type, LuaCSFunction arrayIndexer, LuaCSFunction arrayNewIndexer)
    {
    int top = LuaAPI.lua_gettop(L);
    int meta_idx = abs_idx(top, OBJ_META_IDX);
    int method_idx = abs_idx(top, METHOD_IDX);
    int getter_idx = abs_idx(top, GETTER_IDX);
    int setter_idx = abs_idx(top, SETTER_IDX);

    //begin index gen
    LuaAPI.xlua_pushasciistring(L, "__index");
    LuaAPI.lua_pushvalue(L, method_idx); // 1. 压入 methods 表
    LuaAPI.lua_pushvalue(L, getter_idx); // 2. 压入 getters 表

    if (csIndexer == null)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    // ...
    LuaAPI.lua_pushstdcallcfunction(L, csIndexer); // 3. 压入 csindexer
    // ...
    }

    translator.Push(L, type == null ? base_type : type.BaseType()); // 4. 压入 base

    LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); // 5. 压入 indexfuncs
    if (arrayIndexer == null)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    // ...
    LuaAPI.lua_pushstdcallcfunction(L, arrayIndexer); // 6. 压入 arrayindexer
    // ...
    }

    LuaAPI.gen_obj_indexer(L); // 生成__index 元方法

    if (type != null)
    {
    LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_rawset(L, -3); // 注册表 [LuaIndexs][type] = __index 函数
    LuaAPI.lua_pop(L, 1);
    }

    LuaAPI.lua_rawset(L, meta_idx);
    //end index gen

    //begin newindex gen
    LuaAPI.xlua_pushasciistring(L, "__newindex");
    LuaAPI.lua_pushvalue(L, setter_idx);

    if (csNewIndexer == null)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    // ...
    LuaAPI.lua_pushstdcallcfunction(L, csNewIndexer);
    // ...
    }

    translator.Push(L, type == null ? base_type : type.BaseType());

    LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);

    if (arrayNewIndexer == null)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    // ...
    LuaAPI.lua_pushstdcallcfunction(L, arrayNewIndexer);
    // ...
    }

    LuaAPI.gen_obj_newindexer(L); // 生成__newindex 元方法

    if (type != null)
    {
    LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua newindexs function tables
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_rawset(L, -3); // 注册表 [LuaNewIndexs][type] = __newindex 函数
    LuaAPI.lua_pop(L, 1);
    }

    LuaAPI.lua_rawset(L, meta_idx);
    //end new index gen
    LuaAPI.lua_pop(L, 4);
    }

    __index元方法是通过调用 gen_obj_indexer 获得的,在调用该方法前会依次压入 6 个参数(代码注释中有标注),gen_obj_indexer内部又会再压入一个 nil 值,用于为 baseindex 提前占位。共 7 个参数会作为 upvalue 关联到闭包 obj_indexerobj_indexer 函数就是 __index 元方法,它的逻辑是当访问 userdata[key] 时,先依次查询之前通过 RegisterFunc 填充的 methodsgetters 等表中是否存有对应 key 的包裹方法,如果有则直接使用,如果没有则递归在父类中查找。__newindex元方法是通过调用 gen_obj_newindexer 获得的,与__index 的获得原理类似,这里就不再列出了

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    // xlua.c
    LUA_API int gen_obj_indexer(lua_State *L) {
    lua_pushnil(L);
    lua_pushcclosure(L, obj_indexer, 7);
    return 0;
    }

    //upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex
    //param --- [1]: obj, [2]: key
    LUA_API int obj_indexer(lua_State *L) {
    if (!lua_isnil(L, lua_upvalueindex(1))) { // 如果 methods 中有 key,则使用 methods[key]
    lua_pushvalue(L, 2);
    lua_gettable(L, lua_upvalueindex(1));
    if (!lua_isnil(L, -1)) {//has method
    return 1;
    }
    lua_pop(L, 1);
    }

    if (!lua_isnil(L, lua_upvalueindex(2))) { // 如果 getters 中 key,则调用 getters[key]
    lua_pushvalue(L, 2);
    lua_gettable(L, lua_upvalueindex(2));
    if (!lua_isnil(L, -1)) {//has getter
    lua_pushvalue(L, 1);
    lua_call(L, 1, 1);
    return 1;
    }
    lua_pop(L, 1);
    }

    if (!lua_isnil(L, lua_upvalueindex(6)) && lua_type(L, 2) == LUA_TNUMBER) { // 如果 arrayindexer 中有 key 且 key 是数字,则调用 arrayindexer[key]
    lua_pushvalue(L, lua_upvalueindex(6));
    lua_pushvalue(L, 1);
    lua_pushvalue(L, 2);
    lua_call(L, 2, 1);
    return 1;
    }

    if (!lua_isnil(L, lua_upvalueindex(3))) { // 如果 csindexer 中有 key,则调用 csindexer[key]
    lua_pushvalue(L, lua_upvalueindex(3));
    lua_pushvalue(L, 1);
    lua_pushvalue(L, 2);
    lua_call(L, 2, 2);
    if (lua_toboolean(L, -2)) {
    return 1;
    }
    lua_pop(L, 2);
    }

    if (!lua_isnil(L, lua_upvalueindex(4))) { // 递归向上在 base 中查找
    lua_pushvalue(L, lua_upvalueindex(4));
    while(!lua_isnil(L, -1)) {
    lua_pushvalue(L, -1);
    lua_gettable(L, lua_upvalueindex(5));
    if (!lua_isnil(L, -1)) // found
    {
    lua_replace(L, lua_upvalueindex(7)); //baseindex = indexfuncs[base]
    lua_pop(L, 1);
    break;
    }
    lua_pop(L, 1);
    lua_getfield(L, -1, "BaseType");
    lua_remove(L, -2);
    }
    lua_pushnil(L);
    lua_replace(L, lua_upvalueindex(4));//base = nil
    }

    if (!lua_isnil(L, lua_upvalueindex(7))) {
    lua_settop(L, 2);
    lua_pushvalue(L, lua_upvalueindex(7));
    lua_insert(L, 1);
    lua_call(L, 2, 1); // 调用父类的__index,indexfuncs[base](obj, key)
    return 1;
    } else {
    return 0;
    }
    }
  4. Utils.BeginClassRegister,在对类的静态值(例如静态变量,静态方法等)进行注册前做一些准备工作。主要是为类生成对应的 cls_table 表,以及提前创建好 static_getter 表与 static_setter 表,后续用来存放静态字段对应的 getset包裹方法。注意这里还会为 cls_table 设置元表meta_table

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // Utils.cs
    public static void BeginClassRegister(Type type, RealStatePtr L, LuaCSFunction creator, int class_field_count,
    int static_getter_count, int static_setter_count)
    {
    ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
    LuaAPI.lua_createtable(L, 0, class_field_count);

    LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");
    translator.PushAny(L, type);
    LuaAPI.lua_rawset(L, -3);

    int cls_table = LuaAPI.lua_gettop(L);

    SetCSTable(L, type, cls_table);

    LuaAPI.lua_createtable(L, 0, 3);
    int meta_table = LuaAPI.lua_gettop(L);
    if (creator != null)
    {
    LuaAPI.xlua_pushasciistring(L, "__call");
    #if GEN_CODE_MINIMIZE
    translator.PushCSharpWrapper(L, creator);
    #else
    LuaAPI.lua_pushstdcallcfunction(L, creator);
    #endif
    LuaAPI.lua_rawset(L, -3);
    }

    if (static_getter_count == 0)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    LuaAPI.lua_createtable(L, 0, static_getter_count); // 创建好 static_getter 表
    }

    if (static_setter_count == 0)
    {
    LuaAPI.lua_pushnil(L);
    }
    else
    {
    LuaAPI.lua_createtable(L, 0, static_setter_count); // 创建好 static_setter 表
    }
    LuaAPI.lua_pushvalue(L, meta_table);
    LuaAPI.lua_setmetatable(L, cls_table); // 设置元表
    }

    cls_table 表是根据类的命名空间名逐层添加到注册表中的,主要是通过 SetCSTable 实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    // Utils.cs
    public static void SetCSTable(RealStatePtr L, Type type, int cls_table)
    {
    int oldTop = LuaAPI.lua_gettop(L);
    cls_table = abs_idx(oldTop, cls_table);
    LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);

    List<string> path = getPathOfType(type);

    // 对于 A.B.C 来说

    // for 循环处理 A.B
    // 1. 注册表 [xlua_csharp_namespace][A] = {} 且出栈 注册表 [xlua_csharp_namespace]
    // 2. 注册表 [xlua_csharp_namespace][A][B] = {} 且出栈 注册表 [xlua_csharp_namespace][A]

    for (int i = 0; i < path.Count - 1; ++i)
    {
    LuaAPI.xlua_pushasciistring(L, path[i]);
    if (0 != LuaAPI.xlua_pgettable(L, -2))
    {
    var err = LuaAPI.lua_tostring(L, -1);
    LuaAPI.lua_settop(L, oldTop);
    throw new Exception("SetCSTable for [" + type + "] error: " + err);
    }
    if (LuaAPI.lua_isnil(L, -1)) // 如果 注册表 [xlua_csharp_namespace] 中没有 key path[i] , 则添加一个 path[i] = {} 键值对
    {
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_createtable(L, 0, 0);
    LuaAPI.xlua_pushasciistring(L, path[i]);
    LuaAPI.lua_pushvalue(L, -2);
    LuaAPI.lua_rawset(L, -4);
    }
    else if (!LuaAPI.lua_istable(L, -1))
    {
    LuaAPI.lua_settop(L, oldTop);
    throw new Exception("SetCSTable for [" + type + "] error: ancestors is not a table!");
    }
    LuaAPI.lua_remove(L, -2);
    }

    // 处理 C
    // 注册表 [xlua_csharp_namespace][A][B][C] = cls_table 且出栈 [xlua_csharp_namespace][A][B][C]
    LuaAPI.xlua_pushasciistring(L, path[path.Count - 1]);
    LuaAPI.lua_pushvalue(L, cls_table);
    LuaAPI.lua_rawset(L, -3);
    LuaAPI.lua_pop(L, 1);

    // 在 注册表 [xlua_csharp_namespace] 中添加键值对 [type 对应的 lua 代理 userdata] = cls_table
    LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    ObjectTranslatorPool.Instance.Find(L).PushAny(L, type);
    LuaAPI.lua_pushvalue(L, cls_table);
    LuaAPI.lua_rawset(L, -3);
    LuaAPI.lua_pop(L, 1);
    }

    A.B.C 类为例,将在 Lua 注册表中添加以下表结构,而 Lua 注册表 xlua_csharp_namespace 实际上对应的就是 CS 全局表,所以要在 xLua 中访问 C# 类时才可以直接使用 CS.A.B.C 这样的形式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    Lua 注册表 = {
    xlua_csharp_namespace = { -- 就是 CS 全局表
    A = {
    B = {
    C = cls_table
    }
    },
    },
    }
  5. 多个 Utils.RegisterFunc,与BeginObjectRegisterEndObjectRegister之间的 RegisterFunc 作用相同,将类的每个静态值对应的包裹方法注册到对应的 Lua 表中。静态变量对应的 get 和 set 包裹方法会被分别注册到 static_getter 表和 static_setter 表(只读的静态变量除外)

  6. Utils.EndClassRegister,结束对类的静态值的注册。与 EndObjectRegister 类似,但它是为 cls_table 的元表 meta_tabl 设置 __index 元方法和 __newindex 元方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    // Utils.cs
    public static void EndClassRegister(Type type, RealStatePtr L, ObjectTranslator translator)
    {
    int top = LuaAPI.lua_gettop(L);
    int cls_idx = abs_idx(top, CLS_IDX);
    int cls_getter_idx = abs_idx(top, CLS_GETTER_IDX);
    int cls_setter_idx = abs_idx(top, CLS_SETTER_IDX);
    int cls_meta_idx = abs_idx(top, CLS_META_IDX);

    //begin cls index
    LuaAPI.xlua_pushasciistring(L, "__index");
    LuaAPI.lua_pushvalue(L, cls_getter_idx);
    LuaAPI.lua_pushvalue(L, cls_idx);
    translator.Push(L, type.BaseType());
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    LuaAPI.gen_cls_indexer(L);

    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_rawset(L, -3); // 注册表 [LuaClassIndexs][type] = __index 函数
    LuaAPI.lua_pop(L, 1);

    LuaAPI.lua_rawset(L, cls_meta_idx);
    //end cls index

    //begin cls newindex
    LuaAPI.xlua_pushasciistring(L, "__newindex");
    LuaAPI.lua_pushvalue(L, cls_setter_idx);
    translator.Push(L, type.BaseType());
    LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    LuaAPI.gen_cls_newindexer(L);

    LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua newindexs function tables
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_rawset(L, -3); // 注册表 [LuaClassNewIndexs][type] = __newindex 函数
    LuaAPI.lua_pop(L, 1);

    LuaAPI.lua_rawset(L, cls_meta_idx);
    //end cls newindex

    LuaAPI.lua_pop(L, 4);
    }

上述 6 个部分的代码量比较大,逻辑也比较复杂,到这里有必要做一个总结

生成代码会为类的非静态值都生成对应的包裹方法,并将包裹方法以 key = func 的形式注册到不同的表中。userdata元表的 __index__newindex负责从这不同的表中找到对应 key 的包裹方法,最终通过调用包裹方法实现对 C# 对象的控制

1
2
3
-- lua 测试代码
local obj = CS.TestXLua()
obj.Name = "test" -- 赋值操作将触发 obj 元表的__newindex,__newindex 在 setter 表中找到 Name 对应的 set 包裹方法_s_set_Name,然后通过调用_s_set_Name 方法设置了 TestXLua 对象的 Name 属性为 "test"

生成代码还会为每个类以命名空间为层次结构生成 cls_table 表。与类的非静态值相同,生成代码也会为类的静态值都生成对应的包裹方法并注册到不同的表中(注意这里有些区别,类的静态方法会被直接注册到 cls_table 表中)。而 cls_table 元表的 __index__newindex负责从这不同的表中找到对应 key 的包裹方法,最终通过调用包裹方法实现对 C# 类的控制

1
2
-- lua 测试代码
CS.TestXLua.Test2() -- CS.TestXLua 获取到 TestXLua 类对应的 cls_table,由于 Test2 是静态方法,在 cls_table 中可以直接拿到其对应的包裹方法_m_Test2_xlua_st_,然后通过调用_m_Test2_xlua_st_而间接调用了 TestXLua 类的 Test2 方法

使用反射填充元表

当没有生成代码时,会使用反射进行注册,与生成代码进行注册的逻辑基本相同。通过反射获取到类的各个静态值和非静态值,然后分别注册到不同的表中,以及填充 __index__newindex元方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// Utils.cs
public static void ReflectionWrap(RealStatePtr L, Type type, bool privateAccessible)
{
LuaAPI.lua_checkstack(L, 20);

int top_enter = LuaAPI.lua_gettop(L);
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
//create obj meta table
LuaAPI.luaL_getmetatable(L, type.FullName);
if (LuaAPI.lua_isnil(L, -1))
{
LuaAPI.lua_pop(L, 1);
LuaAPI.luaL_newmetatable(L, type.FullName);
}
// 为元表添加 xlua_tag 标志
LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag());
LuaAPI.lua_pushnumber(L, 1);
LuaAPI.lua_rawset(L, -3); // 元表 [xlua_tag] = 1
int obj_meta = LuaAPI.lua_gettop(L);

LuaAPI.lua_newtable(L);
int cls_meta = LuaAPI.lua_gettop(L);

LuaAPI.lua_newtable(L);
int obj_field = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int obj_getter = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int obj_setter = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int cls_field = LuaAPI.lua_gettop(L);
//set cls_field to namespace
SetCSTable(L, type, cls_field);
//finish set cls_field to namespace
LuaAPI.lua_newtable(L);
int cls_getter = LuaAPI.lua_gettop(L);
LuaAPI.lua_newtable(L);
int cls_setter = LuaAPI.lua_gettop(L);

LuaCSFunction item_getter;
LuaCSFunction item_setter;
makeReflectionWrap(L, type, cls_field, cls_getter, cls_setter, obj_field, obj_getter, obj_setter, obj_meta,
out item_getter, out item_setter, privateAccessible ? (BindingFlags.Public | BindingFlags.NonPublic) : BindingFlags.Public);

// init obj metatable
LuaAPI.xlua_pushasciistring(L, "__gc");
LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
LuaAPI.lua_rawset(L, obj_meta);

LuaAPI.xlua_pushasciistring(L, "__tostring");
LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);
LuaAPI.lua_rawset(L, obj_meta);

LuaAPI.xlua_pushasciistring(L, "__index");
LuaAPI.lua_pushvalue(L, obj_field); // 1.upvalue methods = obj_field
LuaAPI.lua_pushvalue(L, obj_getter); // 2.upvalue getters = obj_getter
translator.PushFixCSFunction(L, item_getter); // 3.upvalue csindexer = item_getter
translator.PushAny(L, type.BaseType()); // 压入 BaseType,4.upvalue base
LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); // 5.upvalue indexfuncs = 注册表 [LuaIndexs]
LuaAPI.lua_pushnil(L); // 6.upvalue arrayindexer = nil
LuaAPI.gen_obj_indexer(L); // 生成__index 函数
//store in lua indexs function tables
LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type); // 压入 type
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3); // 注册表 [LuaIndexs][type] = __index 函数
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_rawset(L, obj_meta); // set __index 即 obj_meta["__index"] = 生成的__index 函数

LuaAPI.xlua_pushasciistring(L, "__newindex");
LuaAPI.lua_pushvalue(L, obj_setter);
translator.PushFixCSFunction(L, item_setter);
translator.Push(L, type.BaseType());
LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
LuaAPI.lua_pushnil(L);
LuaAPI.gen_obj_newindexer(L);
//store in lua newindexs function tables
LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type);
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3); // 注册表 [LuaNewIndexs][type] = __newindex 函数
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_rawset(L, obj_meta); // set __newindex
//finish init obj metatable

LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");
translator.PushAny(L, type);
LuaAPI.lua_rawset(L, cls_field); // cls_field["UnderlyingSystemType"] = type , 记录类的基础类型

if (type != null && type.IsEnum())
{
LuaAPI.xlua_pushasciistring(L, "__CastFrom");
translator.PushFixCSFunction(L, genEnumCastFrom(type));
LuaAPI.lua_rawset(L, cls_field);
}

//init class meta
LuaAPI.xlua_pushasciistring(L, "__index");
LuaAPI.lua_pushvalue(L, cls_getter);
LuaAPI.lua_pushvalue(L, cls_field);
translator.Push(L, type.BaseType());
LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
LuaAPI.gen_cls_indexer(L);
//store in lua indexs function tables
LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type);
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3); // 注册表 [LuaClassIndexs][type] = __index 函数
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_rawset(L, cls_meta); // set __index

LuaAPI.xlua_pushasciistring(L, "__newindex");
LuaAPI.lua_pushvalue(L, cls_setter);
translator.Push(L, type.BaseType());
LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
LuaAPI.gen_cls_newindexer(L);
//store in lua newindexs function tables
LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type);
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3); // // 注册表 [LuaClassNewIndexs][type] = __newindex 函数
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_rawset(L, cls_meta); // set __newindex
// ...
}

调用 C# 方法时参数的传递

先来解决前面遗留的一个问题,对于类的静态值或是非静态值为什么都需要生成对应的包裹方法?其实包裹方法就是用来处理参数传递问题的。

为了正确的和 Lua 通讯,C 函数已经定义好了协议。这个协议定义了参数以及返回值传递方法:C 函数通过 Lua 中的栈来接受参数,参数以正序入栈(第一个参数首先入栈)。因此,当函数开始的时候,lua_gettop(L)可以返回函数收到的参数个数。第一个参数(如果有的话)在索引 1 的地方,而最后一个参数在索引 lua_gettop(L) 处。当需要向 Lua 返回值的时候,C 函数只需要把它们以正序压到堆栈上(第一个返回值最先压入),然后返回这些返回值的个数。在这些返回值之下的,堆栈上的东西都会被 Lua 丢掉。和 Lua 函数一样,从 Lua 中调用 C 函数可以有很多返回值。
也就是说,Lua 这边调用 C 函数时的参数会被自动的压栈,这套机制 Lua 内部已经实现好了。文章开头也提到,C# 可以借助 C/C++ 来与 Lua 进行数据通信,所以 C# 需要通过 C API 获取到 Lua 传递过来的参数,而这个逻辑就被封装在了包裹方法中。以 TestXLuaTest1方法为例,它需要一个 int 参数。所以它的包裹方法需要通过 C API 获取到一个 int 参数,然后再使用这个参数去调用真正的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_Test1(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
{
int _a = LuaAPI.xlua_tointeger(L, 2); // 获取到 int 参数
gen_to_be_invoked.Test1(_a); // 调用真正的 Test1 方法
return 0;
}
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
}

这也解释了为什么需要为类的属性生成对应的 getset方法,因为只有将 Lua 的访问或赋值操作转换成函数调用形式时,参数才能利用函数调用机制被自动的压栈,从而传递给 C#

1
2
3
-- lua 测试代码
obj.Name = "test" -- 赋值操作
setter["Name"]("test") -- 函数调用形式

这里再提一下函数重载的问题,因为 C# 是支持重载的,所以会存在多个同名函数,但参数不同的情况。对于这种情况,只能通过同名函数被调用时传递的参数情况来判断到底应该调用哪个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
[LuaCallCSharp]
public class TestXLua
{
// 函数重载 Test1
public void Test1(int a){
}
// 函数重载 Test1
public void Test1(bool b){
}
}

// 为 Test1 生成的包裹方法
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_Test1(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
int gen_param_count = LuaAPI.lua_gettop(L);
if(gen_param_count == 2&& LuaTypes.LUA_TNUMBER == LuaAPI.lua_type(L, 2)) // 根据参数数量与类型判断调用哪个方法
{
int _a = LuaAPI.xlua_tointeger(L, 2);
gen_to_be_invoked.Test1(_a);
return 0;
}
if(gen_param_count == 2&& LuaTypes.LUA_TBOOLEAN == LuaAPI.lua_type(L, 2)) // 根据参数数量与类型判断调用哪个方法
{
bool _b = LuaAPI.lua_toboolean(L, 2);
gen_to_be_invoked.Test1(_b);
return 0;
}
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
}
return LuaAPI.luaL_error(L, "invalid arguments to TestXLua.Test1!");
}

GC

C# 和 Lua 都是有自动垃圾回收机制的,并且相互是无感知的。如果传递到 Lua 的 C# 对象被 C# 自动回收掉了,而 Lua 这边仍毫不知情继续使用,则必然会导致无法预知的错误。所以基本原则是传递到 Lua 的 C#对象,C# 不能自动回收,只能 Lua 在确定不再使用后通知 C# 进行回收
为了保证 C# 不会自动回收对象,所有传递给 Lua 的对象都会被 objects 保持引用。真实传递给 Lua 的对象索引就是对象在 objects 中的索引
Lua 这边为对象索引建立的userdata 会被保存在缓存表中,而缓存表的引用模式被设置为弱引用

1
2
3
4
5
6
7
8
// ObjectTranslator.cs
LuaAPI.lua_newtable(L); // 创建缓存表
LuaAPI.lua_newtable(L); // 创建元表
LuaAPI.xlua_pushasciistring(L, "__mode");
LuaAPI.xlua_pushasciistring(L, "v");
LuaAPI.lua_rawset(L, -3); // 元表 [__mode] = v,表示这张表的所有值皆为弱引用
LuaAPI.lua_setmetatable(L, -2); // 为缓存表设置元表
cacheRef = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);

当 Lua 这边不再引用这个 userdata 时,userdata会被从缓存表中移除,Lua GC 时会回收这个 userdata,回收之前又会调用 userdata 元表的 __gc 方法,以此来通知 C#,”我 Lua 这边不再使用这个对象了,你该回收可以回收了”。在 BeginObjectRegister方法内部,会为 userdata 的元表添加 __gc 方法

1
2
3
4
5
6
7
// Utils.cs BeginObjectRegister 方法
if ((type == null || !translator.HasCustomOp(type)) && type != typeof(decimal))
{
LuaAPI.xlua_pushasciistring(L, "__gc");
LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
LuaAPI.lua_rawset(L, -3); // 为元表设置__gc 方法
}

translator.metaFunctions.GcMeta实际上就是 StaticLuaCallbacksLuaGC方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// StaticLuaCallbacks.cs
[MonoPInvokeCallback(typeof(LuaCSFunction))]
public static int LuaGC(RealStatePtr L)
{
try
{
int udata = LuaAPI.xlua_tocsobj_safe(L, 1);
if (udata != -1)
{
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
if (translator != null )
{
translator.collectObject(udata);
}
}
return 0;
}
catch (Exception e)
{
return LuaAPI.luaL_error(L, "c# exception in LuaGC:" + e);
}
}

LuaGC方法又会调用 collectObject 方法。在 collectObject 方法内部会将对象从 objects 移除,从而使对象不再被固定引用,能够被 C# GC 正常回收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// ObjectTranslator.cs
internal void collectObject(int obj_index_to_collect)
{
object o;

if (objects.TryGetValue(obj_index_to_collect, out o))
{
objects.Remove(obj_index_to_collect);

if (o != null)
{
int obj_index;
//lua gc 是先把 weak table 移除后再调用__gc,这期间同一个对象可能再次 push 到 lua,关联到新的 index
bool is_enum = o.GetType().IsEnum();
if ((is_enum ? enumMap.TryGetValue(o, out obj_index) : reverseMap.TryGetValue(o, out obj_index))
&& obj_index == obj_index_to_collect)
{
if (is_enum)
{
enumMap.Remove(o);
}
else
{
reverseMap.Remove(o);
}
}
}
}
}

XLua 如何与 C# 进行交互
https://silhouettesforyou.github.io/2024/06/04/04dccfe10a89/
Author
Xiaoming
Posted on
June 4, 2024
Licensed under