繁体   English   中英

使用 Lua 和 C++ 管理堆栈

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM