|
|
@@ -0,0 +1,174 @@
|
|
|
+# C++ 调用 Lua
|
|
|
+
|
|
|
+创建 Lua State
|
|
|
+
|
|
|
+`lua_State` 中存储着一个 Lua 线程所有的状态信息
|
|
|
+
|
|
|
+```cpp
|
|
|
+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` 函数调用后会把栈顶对象弹出
|
|
|
+
|
|
|
+```cpp
|
|
|
+ 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` 
|
|
|
+
|
|
|
+```cpp
|
|
|
+ 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` 并推出栈顶对象,此时栈结构为 `[]`
|
|
|
+
|
|
|
+```c
|
|
|
+ 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`
|
|
|
+
|
|
|
+```c
|
|
|
+ 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);` 表示调用函数,并将函数返回值放到栈中
|
|
|
+
|
|
|
+```cpp
|
|
|
+ lua_getglobal(pL, "require");
|
|
|
+ lua_pushstring(pL, "common/CustomModule");
|
|
|
+ lua_pcall(pL, 1, 0, 0);
|
|
|
+```
|
|
|
+
|
|
|
+详见 `lua_pcall` 函数的定义,`nargs` 表示函数参数个数,`nresults` 表示函数返回值个数
|
|
|
+
|
|
|
+```cpp
|
|
|
+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 代码为例
|
|
|
+
|
|
|
+```lua
|
|
|
+function getXX(a, b)
|
|
|
+ return a, b
|
|
|
+end
|
|
|
+```
|
|
|
+
|
|
|
+使用 C++ 想要调用这个函数,代码逻辑如下
|
|
|
+
|
|
|
+```cpp
|
|
|
+
|
|
|
+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); // 清理两个返回值
|
|
|
+}
|
|
|
+```
|