XLua 如何与 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 |
|
传递 C# 对象到 Lua
对于 bool
,int
这样简单的值类型可以直接通过 C API 传递。但对于 C# 对象就不同了,Lua 这边没有能与之对应的类型,因此传递到 Lua 的只是 C# 对象的一个索引,具体实现请看下面的代码
1 |
|
代码中的两个 if
语句主要是对缓存的判断,如果要传递的对象已经被缓存过了就直接使用缓存的。如果这个对象是被第一次传递,则进行以下两步操作
通过
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;
}通过
xlua_pushcsobj
将代表对象的索引传递到 Lua
参数 key
表示代表对象的索引,参数 meta_ref
表示代表对象类型的表的索引,它的值是通过 getTypeId
函数获得的,后面会详细讲到。参数 need_cache
表示是否需要在 Lua 侧进行缓存,参数 cache_ref
表示 Lua 侧缓存表的索引
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 |
|
函数主要逻辑是以类的名称为 key
通过 luaL_getmetatable
获取类对应的元表,如果获取不到,则通过 TryDelayWrapLoader
函数生成。然后调用 luaL_ref
将获取到的元表添加到 Lua 注册表中,并返回 type_id。type_id 表示的就是元表在 Lua 注册表中的索引,通过这个索引可以在 Lua 注册表中取回元表。前面提到的 xlua_pushcsobj
函数就是利用 type_id
即meta_ref
,获取到元表,然后为 userdata
设置的元表。
下面来看元表具体是怎样生成的
1 |
|
TryDelayWrapLoader 主要用来处理两种情况
- 通过
delayWrap
判断,是否有为该类生成代码,如果有,直接使用生成函数进行填充元表(loader
方法)。在 xLua 的生成代码中有一个 XLuaGenAutoRegister.cs 文件,在这个文件中会为对应的类注册初始化器,而这个初始化器负责将类对应的元表生成函数添加到delayWrap
中。
1 |
|
- 如果没有生成代码,通过反射填充元表(
ReflectionWrap
方法)
使用生成函数填充元表
以LuaCallCSharp
修饰的 TestXLua
类为例来查看生成函数是如何生成的
1 |
|
Generate Code 之后生成的 TestXLuaWrap.cs 如下所示
1 |
|
生成函数 __Register
主要是这样一个框架
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 表
}
}多个
Utils.RegisterFunc
,将类的每个非静态值对应的包裹方法注册到不同的 Lua 表中。包裹方法是 Generate Code 时动态生成的,对于类的属性会生成两个包裹方法,分别是get
和set
包裹方法例如成员方法
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
}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_indexer
。obj_indexer
函数就是__index
元方法,它的逻辑是当访问userdata[key]
时,先依次查询之前通过RegisterFunc
填充的methods
,getters
等表中是否存有对应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;
}
}Utils.BeginClassRegister
,在对类的静态值(例如静态变量,静态方法等)进行注册前做一些准备工作。主要是为类生成对应的cls_table
表,以及提前创建好static_getter
表与static_setter
表,后续用来存放静态字段对应的get
和set
包裹方法。注意这里还会为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
9Lua 注册表 = {
xlua_csharp_namespace = { -- 就是 CS 全局表
A = {
B = {
C = cls_table
}
},
},
}多个
Utils.RegisterFunc
,与BeginObjectRegister
到EndObjectRegister
之间的RegisterFunc
作用相同,将类的每个静态值对应的包裹方法注册到对应的 Lua 表中。静态变量对应的 get 和 set 包裹方法会被分别注册到static_getter
表和static_setter
表(只读的静态变量除外)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 |
|
生成代码还会为每个类以命名空间为层次结构生成 cls_table
表。与类的非静态值相同,生成代码也会为类的静态值都生成对应的包裹方法并注册到不同的表中(注意这里有些区别,类的静态方法会被直接注册到 cls_table
表中)。而 cls_table
元表的 __index
和__newindex
负责从这不同的表中找到对应 key
的包裹方法,最终通过调用包裹方法实现对 C# 类的控制
1 |
|
使用反射填充元表
当没有生成代码时,会使用反射进行注册,与生成代码进行注册的逻辑基本相同。通过反射获取到类的各个静态值和非静态值,然后分别注册到不同的表中,以及填充 __index
和__newindex
元方法
1 |
|
调用 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 传递过来的参数,而这个逻辑就被封装在了包裹方法中。以 TestXLua
的Test1
方法为例,它需要一个 int
参数。所以它的包裹方法需要通过 C API 获取到一个 int
参数,然后再使用这个参数去调用真正的方法
1 |
|
这也解释了为什么需要为类的属性生成对应的 get
和set
方法,因为只有将 Lua 的访问或赋值操作转换成函数调用形式时,参数才能利用函数调用机制被自动的压栈,从而传递给 C#
1 |
|
这里再提一下函数重载的问题,因为 C# 是支持重载的,所以会存在多个同名函数,但参数不同的情况。对于这种情况,只能通过同名函数被调用时传递的参数情况来判断到底应该调用哪个函数
1 |
|
GC
C# 和 Lua 都是有自动垃圾回收机制的,并且相互是无感知的。如果传递到 Lua 的 C# 对象被 C# 自动回收掉了,而 Lua 这边仍毫不知情继续使用,则必然会导致无法预知的错误。所以基本原则是传递到 Lua 的 C#对象,C# 不能自动回收,只能 Lua 在确定不再使用后通知 C# 进行回收
为了保证 C# 不会自动回收对象,所有传递给 Lua 的对象都会被 objects
保持引用。真实传递给 Lua 的对象索引就是对象在 objects
中的索引
Lua 这边为对象索引建立的userdata
会被保存在缓存表中,而缓存表的引用模式被设置为弱引用
1 |
|
当 Lua 这边不再引用这个 userdata
时,userdata
会被从缓存表中移除,Lua GC 时会回收这个 userdata,回收之前又会调用 userdata
元表的 __gc
方法,以此来通知 C#,”我 Lua 这边不再使用这个对象了,你该回收可以回收了”。在 BeginObjectRegister
方法内部,会为 userdata
的元表添加 __gc
方法
1 |
|
translator.metaFunctions.GcMeta
实际上就是 StaticLuaCallbacks
的LuaGC
方法
1 |
|
LuaGC
方法又会调用 collectObject
方法。在 collectObject
方法内部会将对象从 objects
移除,从而使对象不再被固定引用,能够被 C# GC 正常回收
1 |
|