[英]Managing stack with Lua and C++
我需要将 lua 脚本传递给单个字符串(文件路径),并将 0 返回到多个字符串。
int error = 0;
lua_State *L = lua_open();
luaL_openlibs(L);
std::vector<string> list_strings;
用于在加载和调用源文件之前将字符串压入堆栈
if ((error = luaL_loadfile(L, "src/test.lua")) == 0)
{
lua_pushstring(L, path.c_str());
if ((error = lua_pcall(L, 1, LUA_MULTRET, 0)) == 0)
{
lua_gettable(L, LUA_GLOBALSINDEX);
lua_pcall(L,1,1,0);
if (lua_gettop(L) == 1 && lua_istable(L,1))
{
int len = lua_objlen(L,1);
for (int i=1;i =< len; i++)
{
lua_pushinteger(L,i);
lua_gettable(L,1);
const char *s = lua_tostring(L,-1);
if (s)
{
list_strings.push_back(s);
}
lua_pop(L,1);
}
}
}
}
就目前而言,我只是从示例中复制代码,所以我不确定我正在做的事情是否是我想要做的......我想将路径推入堆栈,并调用 lua function从堆栈中取出该值,并将解析与该路径关联的文件。
解析后,它应该返回一个包含其中字符串的表(您可以将其视为搜索特定字符串的 function,我想)
编辑:更清楚。
任何建议/资源? 这里有类似的问题吗? 或任何有用的资源?
我想确保在我们看到你似乎哪里出错之前我明白你在做什么。 您有一个 Lua 脚本文件。 你想执行这个脚本,传递给它一个字符串参数。 它会做一些事情,然后返回零个或多个字符串作为返回值。 你想在你的代码中获取这些值。
好的,让我们从顶部开始:
if ((error = lua_pcall(L, 1, LUA_MULTRET, 0)) == 0)
通常,当您执行lua_pcall
时,第三个参数会告诉 Lua确切地预期有多少返回值。 如果被调用的 function 返回的数量超过此数量,则这些返回值将被丢弃。 如果它返回的数量少于此数字,则使用额外的 NIL 值来填写计数。
LUA_MULTRET 告诉 Lua 不要这样做。 使用它时,所有结果都被压入堆栈。
现在,由于您忽略了发布您的脚本,我不得不猜测您的脚本是什么样的。 您正在返回多个字符串,但您从未说过这是如何发生的。 Lua,作为一种语言,允许多个返回值:
return "string1", "string2";
这导致 2 个字符串被压入堆栈。 这不同于:
return {"string1", "string2"};
这会将一个object 放入堆栈:一张表。 该表包含 2 个字符串。 看到不同?
查看您的代码,您似乎希望 Lua 脚本返回一个字符串表,而不是多个返回值。
在这种情况下,您应该像这样调用 Lua 脚本:
if ((error = lua_pcall(L, 1, 1, 0)) == 0)
这告诉 Lua 你期望一个返回值,如果用户没有提供一个,Lua 会将 NIL 压入堆栈。
现在让我们谈谈堆栈。 在发出 function 调用之前,堆栈的 state 是这样的:
2- {string: path.c_str()}
1- {function: loaded from file "src/test.lua"}
这是从堆栈的顶部到“底部”。 如果您使用我给您的lua_pcall
,您将在堆栈中获得以下内容:
1- {return value}
lua_pcall
将从堆栈中删除参数和 function。 因此它将从堆栈中弹出 N + 1 项,其中 N 是lua_pcall
(第二个参数)指定的 arguments 到 Lua function 的数量。 因此,Lua 将从堆栈中弹出 2 个东西。 然后它将恰好 1 个值压入堆栈:返回值(如果没有返回值,则为 NIL)。
这样我们就可以通过 function 调用。 如果一切顺利,我们现在期望堆栈包含:
1- {table: returned from function}
然而,一切可能并不顺利。 该脚本可能已返回 NIL。 或者是其他东西; 不能保证它是一张桌子。 因此,下一步是验证返回值(注意:这是您的代码不再有意义的地方,所以这是全新的)。
if(lua_istable(L, -1))
lua_istable
完全符合顾名思义:确定给定项目是否为表。 但是那个“-1”是什么意思,为什么它不是你代码中的“1”?
此参数是对堆栈上某个位置的引用。 Lua 的栈也是 Lua 的寄存器文件。 这意味着,与真正的堆栈不同,您可以在堆栈上的任何元素处达到峰值。 堆栈上的元素在堆栈上具有绝对位置。 现在,这就是我们的堆栈的样子:
1- {return value}
我写的那个“1”就是这个值在栈上的绝对位置。 我可以推送值和弹出值,但除非我弹出这个值,否则它的位置将始终为“1”。
然而,它只是“1”,因为我们的堆栈一开始是空的。 假设这一点有点粗鲁(因为如果堆栈不为空,它真的会咬你。Lua 文档在你可以假设堆栈确实是空的时候,state 很有帮助,或者如果它不是,那么堆栈中已经有什么)。 因此,您可以使用相对位置。
这就是“-1”的含义:它是堆栈顶部的第一个堆栈索引。 上面定义的lua_pcall
function 将从堆栈中弹出 2 项(参数和函数),并推送 1 项(返回值或 NIL)。 因此,“-1”将始终指代我们的返回值。
因此,我们检查堆栈索引“-1”(堆栈的顶部)是否是一个表。 如果不是,那就失败。 如果是,那么我们可以解析我们的列表。
这就是我们要进行列表解析的地方。 第一步是获取列表中的项目数:
int len = lua_objlen(L, -1);
list_strings.reserve(len);
第二个只是很好,所以你不会分配很多次。 您确切知道该列表中将包含多少个字符串,因此您不妨提前让列表知道,对吗?
lua_objlen
获取表中数组元素的数量。 请注意,这可以返回零,但我们的循环将处理这种情况。
接下来,我们走过桌子,拉出绳子。
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);
现在,第一个 function 将索引压入堆栈。 之后,我们的堆栈如下所示:
2- {integer: i + 1}
1- {table: returned from function}
lua_gettable
function 值得更多解释。 它需要一个键(请记住:Lua 中的表键不必是整数)和一个表,并返回与该表中该键关联的值。 或 NIL,如果没有关联的值。 但它的工作方式有点奇怪。
它假设堆栈的顶部是关键。 所以它采用的参数是键将索引到的表的堆栈位置。 我们使用“-2”是因为,好吧,看看堆栈。 由于我们推送了 integer,因此该表从顶部算起 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);
这个 function 一次完成所有这些。 如果我们从表中得到的值不是字符串(或数字,因为 Lua 会自动将数字转换为字符串),那么字符串将为theString
。 否则, theString
将有一个 Lua 拥有的指向字符串的指针(不要删除)。 strLen
也将具有字符串的长度。
顺便说一句:Lua 字符串以 NULL 结尾,但它们也可以在内部包含 NULL 字符。 C 字符串不允许这样做,但 C++ std::string
是. 这就是为什么我不像你那样使用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)在调用之前查看堆栈的state... luaL_loadfile 将function 推入堆栈? 还是 lua_pcall?
假设您除了创建它之外还没有对 Lua state 做任何事情,那么在 luaL_loadfile 之前堆栈是空的。 是的,luaL_loadfile 将 function 推入堆栈。 这个 function 代表加载的文件。
3) 如果在调用 function 后返回错误值,堆栈的结果是什么?
正是文档所说的。 现在您了解了堆栈的工作原理,您应该通读文档。 还推荐 Lua 书中的编程。 5.0 版可在线免费获得,但 5.1 版的书要花钱。 5.0 书仍然是一个有用的起点。
4) list_strings.reserve(len); 至于这个......这个 lua 脚本实际上嵌入在一个小的 C 程序中,该程序通过代码库递归,并将收集 lua 脚本返回的所有字符串。保留有效,但我要说的是我将使用许多表将字符串添加到此列表中......在这种情况下是否应该不使用保留? 还是还在用...
std::vector::reserve
确保std::vector
至少包含足够的空间用于 X 元素,其中 X 是您传递给它的值。 我这样做是因为 Lua 告诉您表中有多少元素,因此无需让std::vector
自行扩展。 您可以让它为所有内容分配一个 memory,而不是让std::vector::push_back
function 根据需要分配更多的 memory。
只要您调用一次Lua 脚本,这很有用。 也就是说,它从 Lua 获取单个返回值。 无论返回的表有多大,这都会起作用。 如果您多次调用 Lua 脚本(来自 C++),则无法提前知道要预留多少 memory。 您可以为返回的每个表保留空间,但std::vector
的默认分配方案可能会在大型数据集的分配数量上击败您。 因此,在这种情况下,我不会打扰reserve
。
但是,作为默认情况,从健康大小的reserve
开始并不是不明智的。 选择一个您认为“足够大”的数字,并保留那么多空间。
Lua 侧没有堆栈。 在 C 端推送的值作为 arguments 发送到 Lua 到调用。 如果您正在执行整个脚本,而不是特定的 function,则 arguments 可用作...
所以你可以做local myarg =...
来获得第一个参数。 或local arg ={...}
将它们全部放入表中。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.