前言
lua是一门很小巧的脚本语言,完全是由C实现。正因为它的小巧,所以现在很多项目拿它来做嵌入执行的脚本,这中操作在游戏行业中非常普遍。
1. lua 开发过程中的坑
1.1 薛定谔的table长度
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| local t1 = {
["key1"] = "value1",
["key2"] = "value2",
["key3"] = "value3"
}
local t2 = {
"value1",
"value2",
"value3"
}
local t3 = {
["key1"] = "value1",
"value2"
}
local t4 = {}
local t5 = {nil, nil}
|
上面的5个table, 如果使用 # 或者 table.getn 运算计算table长度的话各为多少呢?
结果是:
t1 size: 0, t2 size: 3, t3 size: 1, t4 size: 0, t5 size: 0
读到这你时候可能有疑问了,t1中明明有三个值,为啥它的长度就为0呢。还有t3它不是有两个值么,为啥它的长度又为1呢
直接结论 就是,# 运算只计算数组值的个数,像key-value这种map形式的元素不参与长度计算
1.2 空与不空
对于最后两个table, 如果如果使用判空操作又会得到什么结果呢?
1
2
3
4
5
6
7
8
9
10
| if t4 == nil then
print("t4 is nil")
else
print("t4 is not nil")
end
if t5 == nil then
print("t5 is nil")
else
print("t5 is not nil")
end
|
执行结果是:
t4 is not nil
t5 is not nil
t4 明明没有任何值,它为啥就不为空呢
原因就是:t4是个空table它有地址所以它不等于nil,类似于C中的分配了地址但是内容为空的情况,这时候判断此地址是否为空,那肯定不为空嘛
1.3 table比较
1
2
3
4
5
6
7
| t6 = {}
t7 = {}
if t6 == t7 then
print("t6 is equal t7")
else
print("t6 is not equal t7")
end
|
执行结果是:
t6 is not equal t7
为啥明明 t6 和 t7 都是空它两就不相等呢?
原因就是: lua中如果是table比较的话,比较的两个table的地址,并不是内容
2. lua 与 C 的互相调用
因为lua是由C实现的,所以其和C的交互非常方便
2.1 C 中运行lua代码
在常用的C 开发中,都会将lua嵌入做脚本语言,方便相关业务快速开发或迭代。
查看lua doc 我们会发现有好几个接口都能执行lua脚本。
lua c doc
lua c api
函数名 | 描述 |
---|
lua_load | Loads a Lua chunk, it does not run it |
luaL_dofile | Loads and runs the given file |
luaL_dostring | Loads and runs the given string |
luaL_loadfile | Loads a file as a Lua chunk, it does not run it |
luaL_loadstring | Loads a string as a Lua chunk, it does not run it |
luaL_loadbuffer | Loads a buffer as a Lua chunk, same as lua_load |
C 中运行lua的简单例子如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| const char *lua_str = " \
local a = 1234 \n \
local b = \"xxyy\" \n \
print(string.format(\"a is %d, b is %s\", a, b)) \
";
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
if (luaL_dostring(L, lua_str) != 0)
{
printf("luaL_dostring failed, %s\n", lua_tostring(L, -1));
}
return 0;
}
|
执行结果:
a is 1234, b is xxyy
2.2 C 导出接口给lua中使用
在平常的开发过程中,我们可能需要导出一些C函数给到业务层的lua调用。C 函数导出后是以动态库的形态被lua加载,然后在lua中以module的形式存在
2.2.1 C 导出普通函数
C 导出函数给到lua调用有两种方式,一种是普通的导出方式,另一种也是“普通”的导出方式
普通的导出方式就是使用lua 的CAPI, luaL_register
例如导出一个简单的hello_world接口:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| int hello_world(lua_State *L)
{
const char *name = luaL_checkstring(L, -1);
printf("from c function, %s tell hello world\n", name);
lua_pushinteger(L, 18); // 返回一个整数
return 1;
}
/* 导出函数列表及函数调用入口 */
static luaL_Reg func_list[] = {
{"hello_world", hello_world},
{NULL, NULL}
};
LUALIB_API int luaopen_test(lua_State *L)
{
luaL_register(L, "test", func_list);
return 1;
}
|
调用上述C库的lua脚本如下:
1
2
3
| local test = require("test")
local output = test.hello_world("zhangshan")
print(string.format("hello_world output is %d", output))
|
执行结果:
from c function, zhangshan tell hello world
hello_world output is 18
另一种“普通”的导出方法参考导出类部分,(●'◡'●)
2.2.2 C中导出lua类
有些操作是附加在某些对象上的,所以我们可能需要在C中导出lua的类
下面就是一个简单的读写文件的操作类的导出:
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
| /* key name in registry */
#define TEST_CALSS_ID "test.class"
typedef struct file_udata {
FILE *fp;
}file_udata_t;
int open_file(lua_State *L) {
const char *file = luaL_checkstring(L, 1);
FILE *fp = fopen(file, "a+");
if (!fp) {
lua_pushnil(L);
return 1;
}
else {
file_udata_t *ui = lua_newuserdata(L, sizeof(file_udata_t));
ui->fp = fp;
luaL_getmetatable(L, TEST_CALSS_ID);
lua_setmetatable(L, -2);
}
return 1;
}
int close_file(lua_State *L) {
file_udata_t *ud = (file_udata_t *)luaL_checkudata(L, 1, TEST_CALSS_ID);
luaL_argcheck(L, ud != NULL, 1, "unexpected object class");
int ret = fclose(ud->fp);
lua_pushboolean(L, ret?0:1);
return 1;
}
int append_file(lua_State *L)
{
file_udata_t *ud = (file_udata_t *)luaL_checkudata(L, 1, TEST_CALSS_ID);
luaL_argcheck(L, ud != NULL, 1, "unexpected object class");
const char *str = luaL_checkstring(L, 2);
size_t size = strlen(str);
if (fwrite(str, 1, size, ud->fp) == size) {
lua_pushboolean(L, 1);
}
else {
lua_pushboolean(L, 0);
}
return 1;
}
int readall_file(lua_State *L)
{
file_udata_t *ud = (file_udata_t *)luaL_checkudata(L, 1, TEST_CALSS_ID);
luaL_argcheck(L, ud != NULL, 1, "unexpected object class");
// get file size
fseek(ud->fp, 0, SEEK_END);
size_t size = ftell(ud->fp);
rewind(ud->fp);
if (size == 0) {
lua_pushnil(L);
return 1;
}
char *out = malloc(size);
if (size != fread(out, 1, size, ud->fp)) {
lua_pushnil(L);
return 1;
}
lua_pushstring(L, out);
free(out);
return 1;
}
static void make_namespace(lua_State *L, const char *name)
{
lua_getglobal(L, name);
if (lua_isnoneornil(L, -1)) {
lua_pop(L, 1);
lua_newtable(L);
lua_setglobal(L, name);
lua_getglobal(L, name);
}
assert(lua_istable(L, -1));
}
static void register_function(lua_State *L, const char *name, lua_CFunction func)
{
lua_pushstring(L, name);
lua_pushcfunction(L, func);
lua_settable(L, -3);
}
LUALIB_API int luaopen_testclass(lua_State *L) {
// regist module private function
luaL_newmetatable(L, TEST_CALSS_ID);
lua_pushstring(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3); /* metatable.__index = metatable */
register_function(L, "appendfile", append_file);
register_function(L, "readallfile", readall_file);
register_function(L, "closefile", close_file);
lua_pop(L, 1);
// registe module global function
make_namespace(L, "testclass");
register_function(L, "openfile", open_file);
lua_pushliteral(L, "__META__");
luaL_getmetatable(L, TEST_CALSS_ID);
lua_rawset(L, -3);
lua_pop(L, 1);
lua_getglobal(L, "testclass");
return 1;
}
|
测试的lua脚本如下:
1
2
3
4
5
6
7
8
9
10
| local tc = require("testclass")
local file = tc.openfile("test.txt")
if file then
file:appendfile("this is a test txt in file")
local str = file:readallfile()
print("test.txt: " .. tostring(str))
file:closefile()
else
print("openfile test.txt failed")
end
|
执行结果:
test.txt: this is a test txt in file
2.2.3 C中执行lua的闭包函数
在业务层写lua脚本的过程中,我们会经常使用到lua的闭包函数,特别是在异步执行的过程中 例如:
1
2
3
4
5
6
7
| local func = function(param, callback)
local str = "param: " .. tostring(param)
callback(1, str)
end
func(123, function(code, msg)
print(string.format("from callback: code is %d msg is %d", code, msg))
end)
|
上面例子中调用的是本地实现的func, lua中参数获取或返回还是比较简单,但是如果此 func 是在C中实现的话,参数传递又该如何操作呢?
依然是实现一个简单的例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| int function_from_c(lua_State *L)
{
int code = luaL_checkint(L, 1);
lua_pushvalue(L, 2); /* copy closure to top */
lua_pushinteger(L, code);
lua_pushstring(L, "c function");
//call the c function, two paramter, no return value
if (lua_pcall(L, 2, 0, 0)) {
printf("error executing callback, error: %s\n", lua_tostring(L, -1));
}
return 0;
}
static luaL_Reg func_list[] = {
{"func", function_from_c},
{NULL, NULL}
};
LUALIB_API int luaopen_testclosure(lua_State *L)
{
luaL_register(L, "testclosure", func_list);
return 1;
}
|
测试的lua脚本:
1
2
3
4
5
6
7
8
9
10
| local ts = require("testclosure")
local func = function(param, callback)
callback(param, "lua function")
end
func(123, function(code, msg)
print(string.format("from callback: code is %d msg is %s", code, msg))
end)
ts.func(123, function(code, msg)
print(string.format("from callback: code is %d msg is %s", code, msg))
end)
|
结果:
from callback: code is 123 msg is lua function
from callback: code is 123 msg is c function
2.2.4 C中的lua栈平衡
由于lua调用C时传参是通过lua的 虚拟栈 实现的,所以这其中就必然涉及到栈平衡的问题。
每当 Lua 调用 C 时,被调用的函数都会获得一个新的堆栈,该堆栈独立于先前的堆栈和仍处于活动状态的 C 函数堆栈。此堆栈包含C函数的参数,并且是C函数将其结果返回给调用者的地方。
栈结构示意图如下:

可以通过 luaL_check* 系列接口获取传递给C函数的参数, 这时候需要确保只获取指定数量的参数,不然后会引发panic。在使用完参数后可以将参数丢弃或者弹出到全局变量等其他地方,这时候也需要确保只从栈中弹出了指定数量的参数,否则也会造成panic。
处理完成后可以使用 luaL_push* 系列接口将返回值放入栈中。这时候C函数的返回值表示需要返回的参数个数,这时候就需要确保栈中有指定数量的值,否则就会引发panic错误。
2.2.5 C中栈交换的问题
a. 在一些特殊的场景我们需要交换两个栈中的值,这时候可以使用 lua_xmove 接口来交换两个栈中的数据
依然是举个栗子:
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
| const char *lua_func_str = " \
local gen_table = function(value1, value2) \n \
return {[\"item1\"] = value1, [\"item2\"] = value2} \n \
end \n \
local gen_int = function(value) \n \
return value + 1 \n \
end \n \
local gen_string = function(value) \n \
return \"value is \" .. tostring(value) \n \
end \n \
return gen_table, gen_int, gen_string \n \
";
static lua_State *lua_func_state = NULL;
static void init_lua_func(void)
{
if (!lua_func_state) {
lua_func_state = luaL_newstate();
}
luaL_openlibs(lua_func_state);
if (luaL_dostring(lua_func_state, lua_func_str) != 0) {
printf("luaL_dostring failed, %s\n", lua_tostring(lua_func_state, -1));
return;
}
// lua function is on stack
if (!lua_isfunction(lua_func_state, 1) || !lua_isfunction(lua_func_state, 2) || !lua_isfunction(lua_func_state, 3)) {
printf("load lua function is failed\n");
return;
}
lua_setglobal(lua_func_state, "gen_string");
lua_setglobal(lua_func_state, "gen_int");
lua_setglobal(lua_func_state, "gen_table");
}
int xchange_int(lua_State *L)
{
int value = luaL_checkint(L, 1);
lua_settop(lua_func_state, 0);
lua_getglobal(lua_func_state, "gen_int");
lua_pushinteger(lua_func_state, (lua_Integer)value);
if (lua_pcall(lua_func_state, 1, 1, 0) || !lua_isnumber(lua_func_state, -1)) {
printf("execute lua function gen_int failed, %s\n", luaL_checkstring(lua_func_state, -1));
lua_pushnil(L);
return 1;
}
lua_settop(L, 0);
// exchange value
lua_xmove(lua_func_state, L, 1);
return 1;
}
int xchange_string(lua_State *L)
{
const char *str = luaL_checkstring(L, 1);
lua_settop(lua_func_state, 0);
lua_getglobal(lua_func_state, "gen_string");
lua_pushstring(lua_func_state, str);
if (lua_pcall(lua_func_state, 1, 1, 0) || !lua_isstring(lua_func_state, 1)) {
printf("execute lua function gen_int failed, %s\n", luaL_checkstring(lua_func_state, -1));
lua_pushnil(L);
return 1;
}
lua_settop(L, 0);
// exchange value
lua_xmove(lua_func_state, L, 1);
return 1;
}
int xchange_table(lua_State *L)
{
const char *value1 = luaL_checkstring(L, 1);
const char *value2 = luaL_checkstring(L, 2);
lua_settop(lua_func_state, 0);
lua_getglobal(lua_func_state, "gen_table");
lua_pushstring(lua_func_state, value1);
lua_pushstring(lua_func_state, value2);
if (lua_pcall(lua_func_state, 2, 1, 0) || !lua_istable(lua_func_state, 1)) {
printf("execute lua function gen_int failed, %s\n", luaL_checkstring(lua_func_state, -1));
lua_pushnil(L);
return 1;
}
lua_settop(L, 0);
// exchange value
lua_xmove(lua_func_state, L, 1);
return 1;
}
static luaL_Reg func_list[] = {
{"xchange_int", xchange_int},
{"xchange_string", xchange_string},
{"xchange_table", xchange_table},
{NULL, NULL}
};
LUALIB_API int luaopen_testexchange(lua_State *L)
{
init_lua_func();
luaL_register(L, "testexchange", func_list);
return 1;
}
|
测试的lua代码如下:
1
2
3
4
5
6
7
| local tx = require("testexchange")
local v1 = tx.xchange_int(10)
print("xchange_int result: " .. tostring(v1))
local v2 = tx.xchange_string("hello")
print("xchange_string result: " .. tostring(v2))
local v3 = tx.xchange_table("zhangsan", "lisi")
print(string.format("xchange_table reault: %s %s", tostring(v3["item1"]), tostring(v3["item2"])))
|
执行结果如下:
xchange_int result: 11
xchange_string result: value is hello
xchange_table reault: nil nil
结论就是: 可以在不同的lua_State交换栈上的普通值(int, string等), 不能交换稍微高级的值(table, userdata等)
b. 改变栈中数据的位置
在C接口编码的过程中,我们的C接口可能需要将当前栈上的参数做一些位置调换,那该怎么办呢?
luac的接口也考虑到了这种需求,只需调用相关的API就可以做相应的位置变换
函数接口 | 说明 |
---|
lua_insert | 将栈顶的元素插入到指定的位置,其他元素相应的往前挪位置 |
lua_replace | 将栈顶元素替换指定位置的元素,其他元素相应的往前挪位置 |