图片来源:https://www.pixiv.net/artworks/82570722
Lua官方解释器完全使用ANSI C编写,并且提供了C API,使用C/C++和Lua交互相当方便
Lua虚拟栈
C和Lua的交互基于一个栈,这个栈的每个位置都有两个索引值,这里只需要记住这个栈的栈顶索引始终为“-1”,栈底索引始终为“1”即可
基本环境加载
后续的代码示例均基于“test.lua”“main.c”这两个文件
-- Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio
a_bool_true = true
a_bool_false = false
a_integer = 214;
a_float_numer = 3.1415926
a_string = "hello"
a_string_has_zero = "hel\0lo"
a_int_arr = {1, 4, 9, 16, 25}
function Concatenate(str1, str2)
return str1..","..str2;
end
rect = {
length = 13,
width = 17
}
function rect:Area()
return self.length * self.width
end
dict = {}
dict[17] = "this is value, key is number 17"
dict["key1"] = "this is value, key is string \"key1\""
dict[Concatenate] = "this is value, key is fuction Concatenate"
#include <stdio.h>
#include <lua.h>
#include <lualib.h>
#include <lauxlib.h>
int main(void){
//创建Lua主线程
lua_State *L = luaL_newstate();
if (!L) {
fprintf(stderr, "Failed to create new Lua state");
}
//加载Lua标准库
luaL_openlibs(L);
//加载test.lua
if (luaL_loadfile(L, "test.lua") || lua_pcall(L, 0, 0, 0)) {
fprintf(stderr, lua_tostring(L, -1));
lua_close(L);
return -1;
}
/*
后续的示例代码应该放在这里运行
*/
//结束运行,释放资源
lua_close(L);
return 0;
}
Lua C API的函数大多第一个参数都为“lua_State *L
”,即“main.c”中使用“luaL_newstate()
”创建的lua住线程,后续不再说明
参考编译命令
gcc main.c `pkg-config --cflags --libs lua`
C获取Lua变量
Lua的C API提供 “lua_getglobal
” 来将指定全局变量压入栈,然后提供一系列“lua_toxxx
”函数来将栈中的变量转换为C类型变量,相关函数在使用前
// name参数是lua脚本中的全局变量名,返回值是变量的lua类型,在lua.h中有相关宏定义
int lua_getglobal (lua_State *L, const char *name);
#define LUA_TNONE (-1)
#define LUA_TNIL 0
#define LUA_TBOOLEAN 1
#define LUA_TLIGHTUSERDATA 2
#define LUA_TNUMBER 3
#define LUA_TSTRING 4
#define LUA_TTABLE 5
#define LUA_TFUNCTION 6
#define LUA_TUSERDATA 7
#define LUA_TTHREAD 8
#define LUA_NUMTYPES 9
// index参数是要转换的变量在栈中的位置
int lua_toboolean (lua_State *L, int index);
lua_Integer lua_tointeger (lua_State *L, int index);
lua_Number lua_tonumber (lua_State *L, int index);
const char *lua_tostring (lua_State *L, int index);
获取布尔值、数值、字符串
布尔值
由于C语言里没有对应布尔类型,会将布尔类型值转换为整形的“1”和“0”。使用“lua_typename
”函数可以将表示lua类型的整数转换为对应C字符串
int type;
type = lua_getglobal(L, "a_bool_true"); // 将a_bool_true变量压入栈
int lua_bool_true = lua_toboolean(L, -1); // 将栈顶变量转换为布尔值,但在C中会将true转换为整数1
// 打印变量lua类型名称(boolean)和变量值(1)
printf("type:%s, value:%d\n", lua_typename(L, type), lua_bool_true);
type = lua_getglobal(L, "a_bool_false"); // 将a_bool_false变量压入栈
int lua_bool_false = lua_toboolean(L, -1); // 将栈顶变量转换为布尔值,但在C中会将false转换为整数0
// 打印变量lua类型名称(boolean)和变量值(0)
printf("type:%s, value:%d\n", lua_typename(L, type), lua_bool_false);
lua_pop(L,2); // 用完之后记得清理栈,第二个参数是要弹出栈的变量的数量
数值
lua脚本中不显式区分整数和浮点数,所以“lua_getglobal
”返回的类型都是“LUA_TNUMBER
” ,但转换为C变量时需要区分,分别使用“lua_tointeger
”和“lua_tonumber
”
type = lua_getglobal(L, "a_integer");
lua_Integer a_lua_integer = lua_tointeger(L, -1); // 将栈顶变量转换为整数
// 打印变量lua类型名称(number)和变量值(214)
printf("type:%s, value:%lld\n", lua_typename(L, type), a_lua_integer);
type = lua_getglobal(L, "a_float_numer");
lua_Number a_float_numer = lua_tonumber(L, -1); // 将栈顶变量转换为浮点数
// 打印变量lua类型名称(number)和变量值(3.141593)
printf("type:%s, value:%lf\n", lua_typename(L, type), a_float_numer);
lua_pop(L,2);
字符串
获取字符串差不多,“lua_tostring
”会在末尾补 '\0'
type = lua_getglobal(L, "a_string");
const char* a_lua_string = lua_tostring(L, -1);
// 打印变量lua类型名称(string)和变量值(hello)
printf("type:%s, value:%s\n", lua_typename(L, type), a_lua_string);
lua_pop(L,1);
但如果字符串本身内容也包含 '\0'
的话无法判断真实长度,此时可以使用“lua_tolstring
”传递第三个参数保存字符串长度
const char *lua_tolstring (lua_State *L, int index, size_t *len);
分别使用“printf
”和“fwrite
”打印获取到的字符串
type = lua_getglobal(L, "a_string_has_zero");
size_t len = ;
// 将栈顶变量转换为字符串,转换后的长度保存到len中
const char* a_string_has_zero = lua_tolstring(L, -1, &len);
// 虽然长度为6,但字符串还是只打印出hel
printf("type:%s, value:%s, len:%d, ", lua_typename(L, type), a_string_has_zero, len);
// 使用fwrite打印完整的字符串,由于\0不可打印,只能看见hello
printf("complete_string:");
fwrite(a_string_has_zero, sizeof(char), len, stdout);
printf("\n");
lua_pop(L,1);
要在命令行确认是否有’\0’输出可以将输出通过管道传递给‘cat -v
’命令,’\0’会以“^@
”的形式显示
./a.out | cat -v
type:string, value:hel, len:6, complete_string:hel^@lo
获取表中的变量
通过变量名获取
获取表中的变量,需要先将表压入栈,然后把这个表在栈中的位置和要获取的变量名传给“lua_getfield
”,“lua_getfield
”会将变量压入栈
// index是表在栈中的索引,k是要获取的变量名
int lua_getfield (lua_State *L, int index, const char *k);
// 先将获取表压入栈
int type_rect = lua_getglobal(L, "rect");
// 将rect.length压入栈
int type_lenght = lua_getfield(L, -1, "length");
lua_Integer rect_length = lua_tointeger(L, -1);
printf("type_rect:%s, type_lenght:%s, rect.length:%d\n",
lua_typename(L, type_rect), lua_typename(L, type_lenght), rect_length);
lua_pop(L,2);
通过key获取
有时候表中成员没有变量名,比如数组和字典,此时可以先将表压入栈,然后通过“lua_pushxxx
”系列函数(或者其他方式)将key压入栈,之后调用“lua_gettable
”将table[key]
压入栈
void lua_pushboolean (lua_State *L, int b);
void lua_pushinteger (lua_State *L, lua_Integer n);
void lua_pushnumber (lua_State *L, lua_Number n);
const char *lua_pushstring (lua_State *L, const char *s);
// index是表在栈中的索引
// 该函数会把栈顶值当作key
// 调用后会自动把key弹出栈,并将table[key]压入栈
int lua_gettable (lua_State *L, int index);
// 将dict表压入栈
lua_getglobal(L, "dict");
// 将整数17压入栈
lua_pushinteger(L, 17);
// 将17弹出栈,并将dict[17]压入栈
lua_gettable(L, -2);
printf("%s\n", lua_tostring(L, -1));
lua_pop(L, 1);
// 将字符串"key1"压入栈
lua_pushstring(L, "key1");
// 将"key1"弹出栈,并将dict["key1"]压入栈
lua_gettable(L, -2);
printf("%s\n", lua_tostring(L, -1));
lua_pop(L, 1);
// 甚至函数也可以作为key, 将全局函数Concatenate压入栈
lua_getglobal(L, "Concatenate");
// 将Concatenate函数弹出栈,并将dict[Concatenate]压入栈
lua_gettable(L, -2);
printf("%s\n", lua_tostring(L, -1));
lua_pop(L, 1);
// 将dict表弹出栈
lua_pop(L, 1);
遍历数组
操作数组前需要先将数组,压入栈,获取数组长度使用“luaL_len
”,该函数会直接返回数组长度。访问数组中的单个值使用“lua_geti
”传入数组在栈中的索引和要访问的数组下标
// index是数组在栈中的索引,返回值为数组长度
lua_Integer luaL_len (lua_State *L, int index);
// index是数组在栈中的索引,i是数组下标
// 调用结束会将arr[i]压入栈
int lua_geti (lua_State *L, int index, lua_Integer i);
// 将数组压入栈
lua_getglobal(L, "a_int_arr");
// 获取数组长度
size_t arr_len = luaL_len(L, -1);
// 注意lua数组下标从1开始
for(size_t i = 1; i <= arr_len; i++){
// 将数组第i个值压入栈
lua_geti(L, -1, i);
printf("%d,",lua_tointeger(L, -1));
// 将数组第i个值弹出栈
lua_pop(L, 1);
}
printf("\n");
// 将数组弹出栈
lua_pop(L, 1);
C调用Lua函数
在C中调用Lua函数需要先将函数压入栈,然后依次将函数参数入栈,调用“lua_pcall
”设置函数参数数量、返回值数量、错误信息位置即可,函数返回值会依次压入栈中
// nargs是函数输入参数数量,nresults是函数返回值数量,msgh设置成0表示调用出错时直接将错误信息入栈
// 返回值为LUA_OK(0)时表示函数调用未出错
// lua_pcall执行后会把lua函数和输入参数弹出栈,然后将lua函数的返回值依次入栈
int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
#define LUA_OK 0
#define LUA_YIELD 1
#define LUA_ERRRUN 2
#define LUA_ERRSYNTAX 3
#define LUA_ERRMEM 4
#define LUA_ERRERR 5
调用全局函数
// 将全局函数Concatenate压入栈
lua_getglobal(L, "Concatenate");
// 将调用Concatenate需要的参入依次压入栈
lua_pushstring(L, "Hello");
lua_pushstring(L, "World!");
// 调用Concatenate,两个输入参数,一个返回值,0表示调用失败时直接将错误信息压入栈
if (lua_pcall(L, 2, 1, 0)) {
fprintf(stderr, lua_tostring(L, -1));
lua_pop(L, 1);
lua_close(L);
return -1;
}
printf("%s\n", lua_tostring(L, -1));
lua_pop(L, 1);
调用对象的成员函数
其实和调用全局函数没太大区别,就是需要先获取对象再获取对象的函数,另外成员函数定义时使用“:”语法省略了self
参数,但C调用的时候不能省略
rect = {
length = 13,
width = 17
}
function rect:Area()
return self.length * self.width
end
// 将rect对象压入栈
lua_getglobal(L, "rect");
// 将rect.Area压入栈, Area定义时使用':'语法省略了self参数,但我们调用时不能省略
lua_getfield(L, -1, "Area");
// 将栈顶移动到-2,即将栈顶两个元素交换位置,这就可以直接将rect作为self参数传给Area
lua_insert(L, -2);
if (lua_pcall(L, 1, 1, 0)) {
fprintf(stderr, lua_tostring(L, -1));
lua_pop(L, 1);
lua_close(L);
return -1;
}
printf("%d\n", lua_tointeger(L, -1));
lua_pop(L, 1);
- Lua官方文档
- 《Lua程序设计(第4版)》
- https://zhuanlan.zhihu.com/p/268407438
本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。