C++与Lua的交互.md 6.7 KB

C++ 调用 Lua

创建 Lua State

lua_State 中存储着一个 Lua 线程所有的状态信息

lua_State* pL = luaL_newstate();

替换库函数

替换库函数为自己的函数,以下面的代码为例

  1. luaL_openlibs(pL); 打开标准库,这里就包含 debug
  2. lua_getglobal(pL, "debug"); 将全局变量 debug 表推入栈顶,此时栈结构为 ["debug"]
  3. lua_getfield(pL, -1, "traceback"); 将 栈顶(-1) 对象的 traceback 字段,并推入到栈顶,此时栈结构为 ["debug", "debug.traceback"]
  4. lua_setfield(pL, -2, "traceback_ori");debug 对象 (-2) 添加名为 traceback_ori 的属性,其值为 栈顶(-1) 对象,并弹出栈顶对象

    1. 此时 debug 有了一个名为 traceback_ori 的属性,并且其功能与 debug.traceback 相同
    2. 此时栈结构为 ["debug"], 因为 lua_setfield 函数调用后会把栈顶对象弹出
  5. lua_pushcfunction(pL, luapdb_debug_traceback); 将 C++ 函数 luapdb_debug_traceback 推入到栈顶,此时栈结构为 ["debug", "luapdb_debug_traceback"]

  6. lua_setfield(pL, -2, "traceback");debug 对象(-2) 设置名为 traceback 的属性,其值为 栈顶(-1) 对象,也就是 luapdb_debug_traceback

    1. 此时 debug.traceback 变成了我们自定义的 C++ 函数 luapdb_debug_traceback,同时原本的 debug.traceback 函数使用 debug.traceback_ori 来替代
    2. 此时栈结构为 ["debug"],因为 lua_setfield 函数调用后会把栈顶对象弹出
	luaL_openlibs(pL);
	lua_getglobal(pL, "debug");
	lua_getfield(pL, -1, "traceback");
	lua_setfield(pL, -2, "traceback_ori");
	lua_getfield(pL, -1, "getupvalue");
	lua_setfield(pL, -2, "getupvalue_ori");
	lua_getfield(pL, -1, "getlocal");
	lua_setfield(pL, -2, "getlocal_ori");
	lua_pushcfunction(pL, luapdb_debug_traceback);
	lua_setfield(pL, -2, "traceback");
	lua_pushcfunction(pL, luapdb_debug_getupvalue);
	lua_setfield(pL, -2, "getupvalue");
	lua_pushcfunction(pL, luapdb_debug_getlocal);
	lua_setfield(pL, -2, "getlocal");
	lua_pop(pL, 1); // pop debug

删除库函数

那么如何理解下面这段代码呢

  1. lua_getglobal(pL, "string"); 将全局变量 string 推入栈顶,此时栈结构为 ["string"]
  2. lua_pushnil(pL); 推入一个 nil 到栈中,此时栈结构为 ["string", "nil"]
  3. lua_setfield(pL, -2, "dump");string (-2)的 dump 属性赋值为栈顶值 nil (-1),并推出栈顶对象,此时栈结构为 ["string"]
  4. lua_pop(pL, 1); 弹出栈顶对象,此时栈结构为 []

综上所属,这段代码等价于 string.dump = nil

	lua_getglobal(pL, "string");
	lua_pushnil(pL);
	lua_setfield(pL, -2, "dump");
	lua_pop(pL, 1);						// string.dump = nil

删除库对象

那么如何理解下面这段代码呢

  1. lua_pushnil(pL); 将 nil 推入栈顶,此时栈结构为 [nil]
  2. lua_setglobal(pL, "loadstring"); 设置全局全局对象 loadstring 的值为栈顶值 nil 并推出栈顶对象,此时栈结构为 []
	lua_pushnil(pL);
	lua_setglobal(pL, "loadstring");	// loadstring = nil

新增全局对象

那么如何理解下面这段代码呢?

  1. lua_newtable(pL); 创建一个 table 对象并放入栈顶,此时栈结构为 [newTable]
  2. lua_pushcfunction(pL, luapdb_replace); 将一个 C++ 函数放入到栈顶,此时栈结构为 [newTable, luapdb_replace]
  3. lua_setfield(pL, -2, "replace");newTable 对象(-2) 设置名为 replace 的属性,其值为 luapdb_replace,并弹出栈顶对象,此时栈结构为 [newTable]
  4. lua_setglobal(pL, "luapdb"); 设置一个全局对象 luapdb 其值为 newTable
	lua_newtable(pL);
	lua_pushcfunction(pL, luapdb_replace);
	lua_setfield(pL, -2, "replace");
	lua_setglobal(pL, "luapdb");

除了 lua_pushcfunction 之外还有其他设置变量值的函数:lua_pushnil、lua_pushlstring、lua_pushinteger、lua_pushnumber、lua_pushstring、lua_pushcclosure 等

函数调用

如何理解下面这段代码呢?

  1. lua_getglobal(pL, "require"); 获取一个全局变量 require 并将其推入栈顶,此时栈结构为 [require]
  2. lua_pushstring(pL, "common/CustomModule"); 推入一个字符串到栈中,此时栈结构为 [require, "common/CustomModule"]
  3. lua_pcall(pL, 1, 0, 0); 表示调用函数,并将函数返回值放到栈中
	lua_getglobal(pL, "require");
	lua_pushstring(pL, "common/CustomModule");
	lua_pcall(pL, 1, 0, 0);

详见 lua_pcall 函数的定义,nargs 表示函数参数个数,nresults 表示函数返回值个数

int lua_pcall(lua_State *L, int nargs, int nresults, int errfunc)

想要通过 C++ 调用 lua 的函数

  1. 首先要把函数入栈,也就是前面第一步做的 lua_getglobal(pL, "require");,此时栈结构为 [函数本体]
  2. 将函数参数从左到右依次入栈,也就是前面第二步做的 lua_pushstring(pL, "common/CustomModule"); ,此时栈结构为 [函数本体,参数1,参数2...]
  3. 使用 lua_pcall ,会从堆栈上取出并弹出 nargs 个参数作为函数的参数列表,再取出此时栈顶的 函数本体,调用函数,并将返回值按照顺序放回到栈中,此时栈结构为 [返回值1,返回值2...]

以下面这个 lua 代码为例

function getXX(a, b) 
	return a, b 
end

使用 C++ 想要调用这个函数,代码逻辑如下


lua_State* L = luaL_newstate(); // 使用luaL_newstate()代替lua_open()

// 加载标准库
luaL_openlibs(L);

// 执行Lua文件
luaL_dofile(L, "test/test.lua");

// 假设 L 已经创建并且该全局函数 getXX 已被定义
lua_getglobal(L, "getXX");        // push 函数 getXX
lua_pushnumber(L, 10);            // 第一个参数 a
lua_pushnumber(L, 20);            // 第二个参数 b

// 调用:2 个参数,期望 2 个返回值,errfunc = 0(没有错误处理器)
int status = lua_pcall(L, 2, 2, 0);

if (status != LUA_OK) {
    // 出错:错误信息在栈顶
    const char *errmsg = lua_tostring(L, -1);
    fprintf(stderr, "lua_pcall error: %s\n", errmsg);
    lua_pop(L, 1); // 弹出错误信息
} else {
    // 成功:返回值在栈上,顺序是 (从低到高) first_return, second_return
    if (lua_isnumber(L, -2) && lua_isnumber(L, -1)) {
        double a_ret = lua_tonumber(L, -2); // 第一个返回值(getXX 返回的 a)
        double b_ret = lua_tonumber(L, -1); // 第二个返回值(getXX 返回的 b)
        printf("returned: %f, %f\n", a_ret, b_ret);	// 输出 10.0000 20.0000
    } else {
        // 类型检查或处理其他返回类型
    }
    lua_pop(L, 2); // 清理两个返回值
}