我想确保在我们发现您似乎哪里出错之前了解您在做什么。你有一个 Lua 脚本文件。你想执行这个脚本,传递给它一个字符串参数。它会做一些事情,然后返回零个或多个字符串作为返回值。你想在你的代码中获取这些值。
好的,让我们从顶部开始:
if ((error = lua_pcall(L, 1, LUA_MULTRET, 0)) == 0)
通常,当您执行lua_pcall 时,第三个参数告诉 Lua 确切需要多少返回值。如果被调用的函数返回的数量超过这个数字,那么这些返回值将被丢弃。如果它返回的数量少于这个数字,则使用额外的 NIL 值来填充计数。
LUA_MULTRET 告诉 Lua 不要这样做。使用时,所有结果都被压入堆栈。
现在,由于您忽略了发布您的脚本,我不得不猜测您的脚本是什么样的。您正在返回多个字符串,但您从未说过这是如何发生的。 Lua 作为一种语言,允许多个返回值:
return "string1", "string2";
这导致 2 个字符串被压入堆栈。这不同于:
return {"string1", "string2"};
这会将一个对象放入堆栈:一张桌子。该表包含 2 个字符串。看出区别了吗?
查看您的代码,您似乎希望 Lua 脚本返回一个字符串表,而不是多个返回值。
在这种情况下,你应该这样调用你的 Lua 脚本:
if ((error = lua_pcall(L, 1, 1, 0)) == 0)
这告诉 Lua 你期望一个返回值,如果用户没有提供,Lua 会将 NIL 压入堆栈。
现在让我们谈谈堆栈。在发出函数调用之前,堆栈的状态是这样的:
2- {string: path.c_str()}
1- {function: loaded from file "src/test.lua"}
这是从堆栈的顶部到“底部”。如果您使用我给您的lua_pcall,您将在堆栈中获得以下内容:
1- {return value}
lua_pcall 将从堆栈中删除参数和函数。因此它将从堆栈中弹出 N + 1 个项目,其中 N 是 lua_pcall(第二个参数)指定的 Lua 函数的参数数量。因此,Lua 会从堆栈中弹出 2 个东西。然后它将恰好 1 个值压入堆栈:返回值(如果没有返回值,则为 NIL)。
这样我们就可以通过函数调用了。如果一切顺利,我们现在期望堆栈包含:
1- {table: returned from function}
然而,一切可能并不顺利。该脚本可能已返回 NIL。或者是其他东西;不能保证它是一张桌子。因此,下一步是验证返回值(注意:这是您的代码不再有意义的地方,所以这是全新的)。
if(lua_istable(L, -1))
lua_istable 完全符合顾名思义:确定给定项目是否为表格。但是那个“-1”是什么意思,为什么它不是你代码中的“1”?
此参数是对堆栈上某个位置的引用。 Lua 的栈也是 Lua 的寄存器文件。这意味着,与 真正的 堆栈不同,您可以在堆栈上的任何元素处达到峰值。堆栈上的元素在堆栈上具有绝对位置。现在,我们的堆栈又是这样的:
1- {return value}
我写的那个“1”就是这个值在栈上的绝对位置。我可以推送值和弹出值,但除非我弹出这个值,否则它的位置将始终为“1”。
然而,它只是“1”,因为我们的堆栈一开始是空。假设这一点有点粗鲁(因为如果堆栈不为空,它真的会咬你。Lua 文档确实有助于说明何时你可以假设堆栈真的是空的,或者如果不是,堆栈上已经有什么)。因此,您可以使用相对位置。
这就是“-1”的含义:它是堆栈顶部的第一个堆栈索引。我们上面定义的lua_pcall 函数将从堆栈中弹出 2 项(参数和函数),并推送 1 项(返回值或 NIL)。因此,“-1”将始终引用我们的返回值。
因此,我们检查堆栈索引“-1”(堆栈顶部)是否为表。如果不是,那就失败。如果是,那么我们可以解析我们的列表。
这就是我们要进行列表解析的地方。第一步是获取列表中的项目数:
int len = lua_objlen(L, -1);
list_strings.reserve(len);
第二个只是很好,所以你不会分配很多次。您确切知道该列表中将包含多少个字符串,所以您不妨提前让列表知道,对吧?
lua_objlen 获取表中数组元素的个数。请注意,这可以返回 zero,但我们的循环会处理这种情况。
接下来,我们走过桌子,拉出绳子。
for (int i=0; i < len; i++) {
//Stuff from below.
}
请记住,Lua 使用 1 基索引。我个人更喜欢在 C/C++ 代码中使用 0 基索引,甚至是与 Lua 接口的代码。所以我尽可能晚地做翻译。但你不必这样做。
现在,对于循环的内容。第一步是从表中获取表项。为此,我们需要给 Lua 一个索引并告诉 Lua 从表中获取该索引:
lua_pushinteger(L, i + 1);
lua_gettable(L, -2);
现在,第一个函数将索引压入堆栈。之后,我们的堆栈如下所示:
2- {integer: i + 1}
1- {table: returned from function}
lua_gettable 函数值得更多解释。它需要一个键(记住:Lua 中的表键不必是整数)和一个表,并返回与该表中该键关联的值。或 NIL,如果没有关联的值。但是它的工作方式有点奇怪。
假设栈顶是键。所以它需要的参数是键将索引到的 table 的堆栈位置。我们使用“-2”是因为,好吧,看看堆栈。由于我们推入了一个整数,因此表格从顶部开始为 2;因此我们使用“-2”。
在此之后,我们的堆栈如下所示:
2- {value: from table[i + 1]}
1- {table: returned from function}
现在我们已经得到了一个值,我们必须验证它是一个字符串,然后得到它的值。
size_t strLen = 0;
const char *theString = lua_tolstring(L, -1, &strLen);
这个函数一次完成所有这些。如果我们从表中得到的值不是字符串(或数字,因为 Lua 会自动将数字转换为字符串),那么 theString 将为 NULL。否则,theString 将有一个 Lua 拥有的指向字符串的指针(不要删除)。 strLen 也会有字符串的长度。
顺便说一句:Lua 字符串以 NULL 结尾,但它们也可以在内部包含 NULL 字符。 C 字符串不允许这样做,但 C++ std::strings 是。这就是为什么我不像你那样使用lua_tostring; C++ 字符串可以完全按原样存储 Lua 字符串。
现在我们有了来自 Lua 的字符串数据,我们需要把它放到我们的列表中。为了避免不必要的复制,我更喜欢这种语法:
list_strings.push_back();
list_strings.back().assign(theString, strLen);
如果我使用支持 C++11 的标准库和编译器,我会使用 list_strings.emplace_back(theString, strLen);,依靠 emplace_back 函数来就地构造 std::string。这巧妙地避免了对字符串进行不必要的复制。
我们需要做最后一点清理工作。我们的堆栈上仍然有两个值:字符串和表格。我们已经完成了字符串,所以我们需要摆脱它。这是通过从 Lua 堆栈中弹出一个条目来完成的:
lua_pop(L, 1);
这里,“1”是要弹出的条目数,而不是堆栈位置。
您现在了解 Lua 中堆栈管理的工作原理了吗?
1) 查看调用前堆栈的状态... luaL_loadfile 将函数压入堆栈?还是 lua_pcall?
假设你除了创建 Lua 状态外还没有对它做任何事情,那么在 luaL_loadfile 之前堆栈是空的。是的,luaL_loadfile 将一个函数压入堆栈。此函数表示已加载的文件。
3) 如果函数调用后返回错误值,堆栈的结果是什么?
究竟是什么the documentation says. 既然您了解了堆栈的工作原理,您应该通读文档。还推荐使用《Lua 编程》一书。 5.0 版是available online for free,但 5.1 版的书要花钱。 5.0 书仍然是一个有用的起点。
4) list_strings.reserve(len);至于这个……这个 lua 脚本实际上嵌入在一个小的 C 程序中,该程序通过代码库递归,并将收集 lua 脚本从所有文件返回的所有字符串……我不知道具体如何保留有效,但我要说的是我将使用许多表将字符串添加到此列表中......在这种情况下是否应该不使用保留?或仍在使用...
std::vector::reserve 确保std::vector 至少包含足够的空间用于 X 元素,其中 X 是您传递给它的值。我这样做是因为 Lua 告诉你表格中有多少元素,所以没有必要让 std::vector 自行扩展。您可以让它为所有内容分配一个内存,而不是让std::vector::push_back 函数根据需要分配更多内存。
只要您调用 Lua 脚本一次,这很有用。也就是说,它从 Lua 获取单个返回值。无论返回的表有多大,这都会起作用。如果您多次调用 Lua 脚本(来自 C++),则无法提前知道要保留多少内存。您可以为返回的每个表保留空间,但 std::vector 的默认分配方案可能会在大型数据集的分配数量上击败您。所以在那种情况下,我不会打扰reserve。
但是,从一个健康大小的reserve 开始并不是不明智的,这是一种默认情况。选择一个您认为“足够大”的数字,并保留那么多空间。