从C ++中遍历Lua表格?

20

我想从Lua加载表格到C++,但是我在正确处理时遇到了问题。第一次迭代进行得很好,但是在第二次调用lua_next时它会崩溃。有什么想法吗?

Lua文件:

level = { 1, 2, 3, }

C++文件 - 首先我做了这个:

lua_getglobal( L, "level" );
for( lua_pushnil( L ); lua_next( L, 1 ); lua_pop( L, -2 ) )
{
    if( lua_isnumber( L, -1 ) ) {
        int i = (int)lua_tonumber( L, -1 );
        //use number
    }
}
lua_pop( L, 1 );

然后我从参考手册尝试了一下:

lua_getglobal( L, "level" );
int t = 1;
lua_pushnil( L );
while( lua_next( L, t ) ) {
    printf( "%s - %s", 
        lua_typename( L, lua_type( L, -2 ) ),
        lua_typename( L, lua_type( L, -1 ) ) );
    lua_pop( L, 1 );
}
lua_pop( L, 1 );

最后是这个:
lua_getglobal( L, "level" );
lua_pushnil( L );

lua_next( L, 1 );
if( lua_isnumber( L, -1 ) ) {
    int i = (int)lua_tonumber( L, -1 );
    //use number fine
}
lua_pop( L, 1 );

lua_next( L, 1 ); //crashes

etc...

自然地,L是一个lua_State*,我正在初始化它并成功解析文件。

编辑: 作为对Jesse Beder答案的回应,我尝试了这段代码,并使用记录器,但我仍然无法使其正常工作。

Log::Get().Write( "engine", "stack size: %i", lua_gettop( L ) );

lua_getglobal(L, "level");
if( lua_istable( L, -1 ) )
    Log::Get().Write( "engine", "-1 is a table" );

lua_pushnil(L);
if( lua_isnil( L, -1 ) )
    Log::Get().Write( "engine", "-1 is now nil" );
if( lua_istable( L, -2 ) )
    Log::Get().Write( "engine", "-2 is now table" );

int pred = lua_next( L, -2 );
Log::Get().Write( "engine", "pred: %i", pred );
while( pred ) {
    Log::Get().Write( "engine", "loop stuff" );
    if( lua_isnumber( L, -1 ) ) {
        int i = (int)lua_tonumber( L, -1 );
        //use number
        Log::Get().Write( "engine", "num: %i", i );
    }
    Log::Get().Write( "engine", "stack size: %i", lua_gettop( L ) );
    if( lua_istable( L, -3 ) )
        Log::Get().Write( "engine", "-3 is now table" );

    lua_pop( L, 1 );
    Log::Get().Write( "engine", "stack size: %i", lua_gettop( L ) );
    if( lua_istable( L, -2 ) )
        Log::Get().Write( "engine", "-2 is now table" );

    pred = lua_next( L, -2 );
    Log::Get().Write( "engine", "pred: %i", pred );
}
lua_pop( L, 1 );

这产生了以下输出:

stack size: 0
-1 is a table
-1 is now nil
-2 is now table
pred: 1
loop stuff
num: 1
stack size: 3
-3 is now table
stack size: 2
-2 is now table

你说的一切,Jesse,似乎都是正确的。但它仍然无法进入下一个迭代。

编辑2: 我尝试将完全相同的代码复制到一个新项目中,跳过所有我没有包含在这里的周围类和其他东西,它可以工作。但在这里它不行,而且它只会存活一个调用lua_next。

编辑3: 我现在进一步缩小了范围。我正在使用hge作为我的2D引擎。 我将所有先前的代码放在test函数中:

test(); //works
if( hge->System_Initiate() )
{       
    test(); //fails
    hge->System_Start();
}

据我所知,hge与lua无关。 这是我制作的一个小测试的源代码在这里。hge 1.81的源代码在这里编辑4: 问题规模已经失控,但没办法。这是我能够将其缩小到的最小代码。
extern "C"
{
    #include <lua/lua.h>
    #include <lua/lualib.h>
    #include <lua/lauxlib.h>
}
#include <hge\hge.h>

bool frame_func()
{   
    return true;
}

bool render_func()
{
    return false;
}

void test()
{
    lua_State *L = lua_open();
    luaL_openlibs( L );

    if( luaL_dofile( L, "levels.lua" ) ) {
        lua_pop( L, -1 );
        return;
    }
    lua_getglobal(L, "level");
    lua_pushnil(L);

    while( lua_next( L, -2 ) ) {
        if( lua_isnumber( L, -1 ) ) {
            int i = (int)lua_tonumber( L, -1 );
            //use number
        }
        lua_pop( L, 1 );
    }
    lua_pop( L, 1 );

    lua_close( L );
}

int main()
{
    HGE *hge = hgeCreate( HGE_VERSION );

    hge->System_SetState( HGE_FRAMEFUNC, frame_func );
    hge->System_SetState( HGE_RENDERFUNC, render_func );
    hge->System_SetState( HGE_WINDOWED, true );
    hge->System_SetState( HGE_SCREENWIDTH, 800 );
    hge->System_SetState( HGE_SCREENHEIGHT, 600 );
    hge->System_SetState( HGE_SCREENBPP, 32 );

    //test(); //works

    if( hge->System_Initiate() )
    {       
        test(); //fails
        hge->System_Start();
    }

    hge->Release();

    return 0;
}

所以第二次调用 lua_next 崩溃了?这很奇怪... 你有任何关于崩溃的调试信息吗(比如它在哪里崩溃了)?另外,为了确保一切正常工作,您应该在每个步骤记录键(它应该也是一个数字),并编辑此答案。 - Jesse Beder
我添加了lua_next的返回值。我没有任何调试信息,也不知道如何添加它... - Jonas
那第二次编辑是一个提示 - 查看我回答的编辑。 - Jesse Beder
你在什么时候初始化lua状态?如果你将其推迟到“System_Initiate”之后,会发生什么?你何时调用“hgeCreate”?我想知道它们是否都使用了一些冲突的内存池。看起来你确实可以同时使用lua和hge(谷歌“lua hge”),但也许在初始化时需要做一些特殊的处理? - Jesse Beder
我已经尝试过在之前和之后初始化lua状态,但没有结果。hgeCreate被调用得早得多。我不明白为什么,虽然我以前使用过lua,但没有使用过lua_next。也许是我做的其他事情搞砸了一切?但我就是看不出来。 - Jonas
Glen Maynard为Visual Studio创建了一些有用的调试信息,这使得解析lua_State对象变得更加容易。指令在此处:http://lua-users.org/lists/lua-l/2006-10/msg00491.html - tenpn
3个回答

31

当你调用lua_next时,第二个参数应该是表的索引。由于您只需要使用 将表推送到堆栈上,所以......

当您调用lua_next时,请确保第二个参数传递表的索引。因为您仅需使用堆栈推送表即可。
lua_getglobal(L, "level");

在那之后,调用堆栈的情况将会是:

-1:表 "level"

(不是 +1,因为堆栈从上往下读取)。然后你调用

lua_pushnil(L);

因此,您的堆栈将如下所示:

-1:键(空)
-2:表“level”

您的表位于-2,因此在调用lua_next时,应使用索引-2。最后,在每次迭代之后,您的堆栈应该如下所示:

-1:值
-2:键
-3:表“level”

因此,您想要读取值(位于-1),然后弹出它(只需弹出一次),然后调用lua_next以获取下一个键。因此,类似以下内容应该可以工作:

lua_getglobal(L, "level");
lua_pushnil(L);

while(lua_next(L, -2)) {  // <== here is your mistake
    if(lua_isnumber(L, -1)) {
        int i = (int)lua_tonumber(L, -1);
        //use number
    }
    lua_pop(L, 1);
}
lua_pop(L, 1);

根据您的第二次编辑进行编辑

既然在删除外部内容时可以正常工作,但重新添加外部内容后不能正常工作,我最好的猜测是您以某种方式破坏了堆栈(无论是C++堆栈还是lua堆栈)。请特别仔细地查看指针,尤其是在操作lua状态时。


我进一步缩小了范围,这里还有另一个编辑需要您处理。 - Jonas
这个问题可能很久远,也许永远都不会有答案。但是为什么在遍历表之前我们必须使用lua_pushnil呢? - Joel
在2012年的这个 Stack Overflow回答的评论中找到了我的答案。评论中说“你从一个值为nil的‘键’开始,这告诉lua_next获取‘第一个’键”,这帮助我理解了为什么需要这样做。 - Joel

1

阅读LUA手册lua_next,您会发现在直接使用lua_tostring转换键时可能会出现问题,也就是说,您必须检查键的值,然后决定使用lua_tostring还是lua_tonumber。因此,您可以尝试使用以下代码:

   std::string key
   while(lua_next(L, -2) != 0){ // in your case index may not be -2, check

    // uses 'key' (at index -2) and 'value' (at index -1)

    if (lua_type(L, -2)==LUA_TSTRING){ // check if key is a string
         // you may use key.assign(lua_tostring(L,-2));
    }
    else if (lua_type(L, -2)==LUA_TNUMBER){ //or if it is a number
         // this is likely to be your case since you table level = { 1, 2, 3, }
         // don't declare field ID's
         // try:
         // sprintf(buf,"%g",lua_tonumber(L,-2));
         // key.assign(buf);
    }
    else{
        // do some other stuff
    }
    key.clear();
    lua_pop(L,1)
   }

希望它有所帮助。

0
为什么您在参考手册版本的结尾额外添加了 lua_pop(L, 1)?我可以看出这可能是一个问题,因为您超出了堆栈深度。

我正在将表格从堆栈中移除,在手册中只有一个表格索引,但是您也必须在某个地方执行push和pop操作。 - Jonas

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接