【问题标题】:Managing stack with Lua and C++使用 Lua 和 C++ 管理堆栈
【发布时间】:2011-09-24 13:58:31
【问题描述】:

我需要向 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 函数从堆栈中取出该值,并将解析与该路径关联的文件。

解析后,它应该返回一个包含其中字符串的表(我想你可以把它想象成一个搜索特定字符串的函数)

编辑:更清楚。

任何建议/资源? 这里有类似的问题吗?或任何有用的资源?

【问题讨论】:

  • 我不知道如何回答你的问题......但如果你可以“将字符串推入堆栈”,你当然也可以推入一个数字(即使你必须先将其转换为字符串) .然后在 Lua 端得到那个数字,你就知道有多少个字符串了 :)
  • 尝试编写多语言源文件,除此之外,还要与另一种语言交互,这绝非易事。祝你好运!我建议你只使用 C 或 C++ 中的 1 个
  • 我认为提供更多信息和细节会让您更有可能得到一个好的答案。
  • @Omnifarious 我编辑了更多细节。
  • 你是什么意思使用“仅 C 或 C++ 的 1”,也是我与 Lua 交互的原因部分是为了经验,部分是为了重用我在 C++ 中构建的代码库,它可以工作真的很好解决问题,但我想用更友好的语言进行解析。

标签: c++ lua


【解决方案1】:

Lua 端没有堆栈。在 C 端推送的值作为调用的参数发送到 Lua。如果您正在执行整个脚本,而不是特定函数,则参数可用作 ...。所以你可以通过local myarg = ... 来获取第一个参数。或local arg ={...}将它们全部放入一个表中。

【讨论】:

  • 那么,如果我推送的第一个元素是一个字符串值,那么我用local myarg = ... 调用的lua 中的第一个元素会是那个值吗?我可以请您看一下我从另一个示例中复制过来的代码吗?在其中,在执行 lua 调用后,原始程序员在 for 循环中从堆栈中取出值,但他也将整数推送到堆栈中,我不明白为什么,或者它是如何工作的。
  • @Colton,对不起,我不明白你的问题。可以发一些代码吗?
  • @lhf 对不起。原始帖子已编辑,我的评论已修复。
  • @Colton,如果你想将参数传递给脚本,你应该在lua_pcall之前但在luaL_loadfile之后推送它们。另外,请确保在lua_pcall 中使用nargs=1
  • @lhf 我对其进行了编辑和更改,以便 lua_pcall 为 nargs 取 1,并且在加载文件之后发生推送字符串。调用中的 LUA_MULTRET 宏是什么意思?
【解决方案2】:

我想确保在我们发现您似乎哪里出错之前了解您在做什么。你有一个 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 开始并不是不明智的,这是一种默认情况。选择一个您认为“足够大”的数字,并保留那么多空间。

【讨论】:

  • 哇。这是一个非常棒的答案。
  • 哇。这是一个非常棒的答案。问题: 1)查看调用前的堆栈状态... luaL_loadfile 将函数推送到堆栈?还是 lua_pcall? 2) 我需要做哪些更改来修改此程序以调用 lua 脚本中的特定 lua 功能,而不是整个脚本文件? 3) 如果在调用函数后返回错误值,堆栈的结果会是什么?
  • 4) list_strings.reserve(len);至于这个……这个 lua 脚本实际上嵌入在一个小的 C 程序中,该程序通过代码库递归,并将收集 lua 脚本从所有文件返回的所有字符串……我不知道具体如何保留有效,但我要说的是我将使用许多表将字符串添加到此列表中......在这种情况下是否应该不使用保留?或仍在使用...
  • @Colton:问题 2 足够大,可以成为它自己的问题,而不是对你已有的问题的补充。问题 1 和 3 应放入您的原始问题中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-10-10
  • 1970-01-01
  • 2012-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-04-28
相关资源
最近更新 更多