使用Lua和C++管理堆栈

Col*_*ips 7 c++ lua

我需要传递一个lua脚本一个字符串(文件路径),并返回0到许多字符串.

int error = 0;
lua_State *L = lua_open();
luaL_openlibs(L);

std::vector<string> list_strings;
Run Code Online (Sandbox Code Playgroud)

在加载和调用源文件之前,用于将字符串压入堆栈

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);
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

就目前而言,我刚刚从示例中复制代码,所以我不确定我正在做的是我想做什么...我想把路径推到堆栈上,然后调用一个lua函数从堆栈中取出该值,并将解析与该路径关联的文件.

在解析之后,它应该返回一个包含其内部字符串的表(您可以将其视为搜索特定字符串的函数,我想)

编辑:做得更清楚.

有什么建议/资源吗?这里有类似的问题吗?还是有用的资源?

Nic*_*las 31

我想确保在看到你出错的地方之前我明白你在做什么.你有一个Lua脚本文件.您想要执行此脚本,并将其传递给单个字符串参数.它将执行一些操作,然后返回零个或多个字符串作为返回值.并且您希望在代码中获取这些值.

好的,让我们从顶部开始:

if ((error = lua_pcall(L, 1, LUA_MULTRET, 0)) == 0)
Run Code Online (Sandbox Code Playgroud)

通常,当您执行a时lua_pcall,第三个参数会告诉Lua 确切的预期返回值.如果被调用的函数返回的值超过此数字,则丢弃这些返回值.如果它返回的数量少于此数字,则使用其他NIL值来填充计数.

LUA_MULTRET告诉Lua不要这样做.使用它时,所有结果都被压入堆栈.

现在,既然你没有发布你的脚本,我必须猜测你的脚本是什么样的.你正在返回多个字符串,但你永远不会说这是怎么回事.Lua作为一种语言,允许多个返回值:

return "string1", "string2";
Run Code Online (Sandbox Code Playgroud)

这导致2个字符串被压入堆栈.这不同于:

return {"string1", "string2"};
Run Code Online (Sandbox Code Playgroud)

这会将一个对象放入堆栈:表.该表包含2个字符串.看到不同?

看看你的代码,你似乎期望Lua脚本返回一个字符串,而不是多个返回值.

在这种情况下,您应该像这样调用您的Lua脚本:

if ((error = lua_pcall(L, 1, 1, 0)) == 0)
Run Code Online (Sandbox Code Playgroud)

这告诉Lua你期望一个返回值,如果用户没有提供一个返回值,Lua会将NIL推入堆栈.

现在让我们谈谈堆栈.在发出函数调用之前,堆栈的状态是这样的:

2- {string: path.c_str()}
1- {function: loaded from file "src/test.lua"}
Run Code Online (Sandbox Code Playgroud)

这是从堆栈的顶部到"底部".如果你使用lua_pcall我给你的那个,你将在你的堆栈上获得以下内容:

1- {return value}
Run Code Online (Sandbox Code Playgroud)

lua_pcall除去从堆栈参数(一个或多个)和功能.因此它将从堆栈中弹出N + 1个项目,其中N是由lua_pcall(第二个参数)指定的Lua函数的参数个数.因此,Lua会从堆栈中弹出2件事.然后它会将1个值精确地推送到堆栈:返回值(如果没有返回值则为NIL).

这样我们就可以通过函数调用了.如果一切顺利,我们现在希望堆栈包含:

1- {table: returned from function}
Run Code Online (Sandbox Code Playgroud)

然而,一切都可能不顺利.该脚本可能已返回NIL.或者是其他东西; 不能保证它是一张桌子.因此,下一步是验证返回值(注意:这是您的代码停止有意义的地方,所以这是全新的).

if(lua_istable(L, -1))
Run Code Online (Sandbox Code Playgroud)

lua_istable完全符合名称的建议:确定给定项目是否为表格.但是那个"-1"是什么意思,为什么它不是你代码中的"1"?

此参数是对堆栈上的位置的引用.Lua的堆栈也是Lua的寄存器文件.这意味着,与真实堆栈不同,您可以在堆栈上的任何元素处达到峰值.堆栈上的元素在堆栈上具有绝对位置.现在,这是我们的堆栈再次出现的情况:

1- {return value}
Run Code Online (Sandbox Code Playgroud)

我写的那个"1"是这个值堆栈的绝对位置.我可以推送值和弹出值,但除非我弹出这个值,否则它的位置将始终为"1".

但是,它只是"1",因为我们的堆栈开始是空的.假设这一点有点粗鲁(因为如果堆栈不是空的话,它可以真正咬你.当你可以假设堆栈真的是空的时候,或者如果它不是,堆栈中已有的东西,Lua文档会有用地说明.)因此,您可以使用相对位置.

这就是"-1":它是堆栈顶部的第一个堆栈索引.我们lua_pcall上面定义的函数将从堆栈(参数和函数)中弹出2个项目,并按下1个项目(返回值或NIL).因此," - 1"将始终引用我们的返回值.

因此,我们检查堆栈索引"-1"(堆栈顶部)是否是表.如果不是,那就失败了.如果是,那么我们可以解析我们的列表.

这就是我们列出解析的地方.第一步是获取列表中的项目数:

int len = lua_objlen(L, -1);
list_strings.reserve(len);
Run Code Online (Sandbox Code Playgroud)

第二个只是一个精确的,所以你没有分配很多次.您确切知道该列表中将包含多少字符串,因此您可以提前知道列表,对吧?

lua_objlen获取表中的数组元素数.请注意,这可以返回,但我们的循环将处理该情况.

接下来,我们走出桌子,拔出琴弦.

for (int i=0; i < len; i++) {
    //Stuff from below.
}
Run Code Online (Sandbox Code Playgroud)

请记住,Lua使用1个基数的索引.我个人更喜欢在C/C++代码中使用0基索引,甚至是与Lua接口的代码.所以我尽可能晚地进行翻译.但你没必要.

现在,为循环的内容.第一步是从表中获取表条目.要做到这一点,我们需要给Lua一个索引并告诉Lua从表中获取该索引:

lua_pushinteger(L, i + 1);
lua_gettable(L, -2);
Run Code Online (Sandbox Code Playgroud)

现在,第一个函数将索引压入堆栈.之后,我们的堆栈看起来像这样:

2- {integer: i + 1}
1- {table: returned from function}
Run Code Online (Sandbox Code Playgroud)

lua_gettable功能值得更多解释.它需要一个键(记住:Lua中的表键不必是整数)和一个表,并返回该表中与该键相关的值.或NIL,如果没有关联的值.但它的工作方式有点奇怪.

它假设堆栈的顶部是关键.因此,它所采用的参数是密钥将索引到的的堆栈位置.我们使用"-2"因为,看看堆栈.因为我们推了一个整数,所以表从顶部开始是2; 因此我们使用"-2".

在此之后,我们的堆栈看起来像这样:

2- {value: from table[i + 1]}
1- {table: returned from function}
Run Code Online (Sandbox Code Playgroud)

现在我们已经获得了一个值,我们必须验证它是一个字符串,然后获取它的值.

size_t strLen = 0;
const char *theString = lua_tolstring(L, -1, &strLen);
Run Code Online (Sandbox Code Playgroud)

此功能可以同时执行所有这些操作.如果我们从表中获得的值不是字符串(或数字,因为Lua会自动将数字转换为字符串),那么theString将为NULL.否则,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);
Run Code Online (Sandbox Code Playgroud)

如果我使用的是支持C++ 11的标准库和编译器,我会使用它list_strings.emplace_back(theString, strLen);,依靠emplace_back函数来构建std::string就地.这样可以避免制作更多的字符串副本.

我们需要做最后一点清理工作.我们的堆栈上还有两个值:字符串和表.我们已经完成了字符串,所以我们需要摆脱它.这是通过从Lua堆栈弹出一个条目来完成的:

lua_pop(L, 1);
Run Code Online (Sandbox Code Playgroud)

这里,"1"是要弹出的条目数,而不是堆栈位置.

您是否了解堆栈管理现在如何在Lua中运行?


1)在调用之前查看堆栈的状态... luaL_loadfile将函数推送到堆栈?还是lua_pcall?

假设除了创建它之外你还没有对Lua状态做过任何事情,那么在luaL_loadfile之前堆栈是空的.是的,luaL_loadfile将一个函数推送到堆栈.此函数表示已加载的文件.

3)堆栈的结果是什么,如果在进行函数调用后返回错误值?

正是文档所说的.既然您已了解堆栈的工作原理,那么您应该阅读文档.还建议使用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自己扩展.您可以为所有内容分配一个内存,而不是让std::vector::push_back函数根据需要分配更多内存.

只要您调用一次 Lua脚本,这就很有用.也就是说,它从Lua获得单个返回值.无论返回的表有多大,这都行.如果你多次调用你的Lua脚本(来自C++),那么就没有办法提前知道要保留多少内存.您可以为每个返回的表保留空间,但是std::vector对于大型数据集的分配数量,默认分配方案可能会超过您.那么在那种情况下,我不会打扰reserve.

然而,从健康大小开始reserve,作为一种默认情况并不是不明智的.选择一个你认为"足够大"的数字,并保留那么多空间.