【问题标题】:How to have a Lua iterator return a C struct?如何让 Lua 迭代器返回 C 结构?
【发布时间】:2012-08-23 16:32:59
【问题描述】:

我有一个使用 Lua 5.2.1 的 Visual Studio 2008 C++03 项目,我希望迭代器返回一个对象,以便我可以获取参数值或调用相关函数。 例如:

for f in foo.list() do
    if f:bar() then
        print("success")
    else
        print("failed")
    end
    print(string.format( "%d: %s", f.id, f.name))
end

我正在使用以下 C++ 代码来实现这一点(省略了错误检查):

struct Foo {
    int id;
    char name[ 256 ];
    HANDLE foo_handle;
}

int foo_list( lua_State* L )
{
    Foo* f = ( Foo* )lua_newuserdata( L, sizeof( Foo ) );
    ZeroMemory( f, sizeof( Foo ) );
    luaL_getmetatable( L, foo_metatable );
    lua_setmetatable( L, -2 );
    f->foo_handle = CreateFooHandle();
    lua_pushcclosure( L, foo_iter, 1 );
    return 1;
}

int foo_iter( lua_State* L )
{
    Foo* foo = ( Foo* )lua_touserdata( L, lua_upvalueindex( 1 ) );
    if( GetNextFoo( foo ) ) /*sets the id and name parameters*/
    {
        // is this correct? I need to return some object...
        luaL_getmetatable( L, foo_metatable );
        return 1;
    }
    return 0;
}

int foo_name( lua_State* L )
{
    Foo* f = ( Foo* )luaL_checkudata( L, 1, foo_metatable );
    lua_pushstring( L, f->name );
    return 1;
}

int foo_id( lua_State* L )
{
    Foo* f = ( Foo* )luaL_checkudata( L, 1, foo_metatable );
    lua_pushinteger( L, f->id );
    return 1;
}

int foo_bar( lua_State* L )
{
    Foo* f = ( Foo* )luaL_checkudata( L, 1, foo_metatable );
    if( FooBar( f ) )
        lua_pushboolean( L, true );
    else
        lua_pushboolean( L, false );
    return 1;
}

int foo_close( lua_State* L ) { /*omitted. this part works*/ }

extern "C" int luaopen_foo( lua_State* L )
{
    // how do I differentiate between a parameter get and a function call?
    const luaL_Reg foo_methods[] = { 
        { "name", foo_name },
        { "id", foo_id },
        { "bar", foo_bar },
        { "__gc", foo_close },
        { NULL, NULL }
    };
    luaL_newmetatable( L, foo_metatable );
    luaL_setfuncs( L, foo_methods, 0 );

    const luaL_Reg foo[] = { 
        { "list", foo_list }
        { NULL, NULL }
    };
    luaL_newlib( L, foo );

    return 1;
}

但是,当我运行它时,我得到了 Lua 错误:foo.lua:2: calling 'bar' on bad self

我知道有一些包装器可以做到这一点,但我更愿意在实现任何包装器之前了解底层 Lua 机制。

【问题讨论】:

    标签: c++ lua


    【解决方案1】:

    您正在从迭代器返回一个元表,而不是 foo 实例。

    更重要的是,您的元表包含方法,但没有元方法。特别是,如果您希望 foo 方法调用解析为元表中的方法,则需要设置 __index 元方法。

    我建议在通过 C API 实现之前学习元表在 Lua 中的工作原理。

    当您说foo.id 时,如果foo 中不存在id(或foo 是用户数据)并且foo 有一个带有__index 集的元表,这将解析为以下之一两件事:

    1. 如果__index 是一个函数,该函数将使用字符串id 调用,foo.id 解析为该函数返回的任何内容。
    2. 如果__index 是一个表,则存储在rawget(__index, 'id') 中的值因此foo.id 基本上解析为`rawget(getmetatable(foo).__index, 'id')。

    所以如果你想使用foo:id(),你可以创建一个通用的id方法foo的元表,它返回值self.id

    如果您想使用foo.id,您要么需要将foo 更改为表格并将id 作为其状态的一部分存储,要么将__index 实现为您进行字符串比较并识别的函数id 应该解析为 self.id


    这是您的代码的修改简化版本,显示 __index 元方法有效:

    static int nextFooId = 0;
    struct Foo {
       int id;
       char name[ 256 ];
    };
    
    static const char* foo_metatable = "foo";
    
    int foo_iter( lua_State* L )
    {
       if (++nextFooId >= 10)
          return 0;
    
       // create and initialize foo
       Foo* foo = ( Foo* )lua_newuserdata( L, sizeof( Foo ) );
       foo->id = nextFooId;
       sprintf(foo->name, "Foo %d", foo->id);
    
       // set metatable for foo
       luaL_getmetatable( L, foo_metatable );
       lua_setmetatable( L, -2 );
       return 1;
    }
    
    
    int foo_list( lua_State* L )
    {
       lua_pushcclosure( L, foo_iter, 1 );
       return 1;
    }
    
    int foo_name( lua_State* L )
    {
       Foo* f = ( Foo* )luaL_checkudata( L, 1, foo_metatable );
       lua_pushstring( L, f->name );
       return 1;
    }
    
    int foo_id( lua_State* L )
    {
       Foo* f = ( Foo* )luaL_checkudata( L, 1, foo_metatable );
       lua_pushinteger( L, f->id );
       return 1;
    }
    
    int foo_bar( lua_State* L )
    {
       lua_pushboolean( L, rand()%2 );
       return 1;
    }
    
    int foo_close( lua_State* L ) { return 0;/*omitted. this part works*/ }
    
    extern "C" int luaopen_foo( lua_State* L )
    {
       const luaL_Reg foo_methods[] = { 
          { "name", foo_name },
          { "id", foo_id },
          { "bar", foo_bar },
          { "__gc", foo_close },
          { NULL, NULL }
       };
       luaL_newmetatable( L, foo_metatable );
       luaL_setfuncs( L, foo_methods, 0 );
    
       // copy the metatable to the top of the stack 
       // and set it as the __index value in the metatable
       lua_pushvalue(L, -1);
       lua_setfield( L, -2, "__index");
    
       const luaL_Reg foo[] = { 
          { "list", foo_list },
          { NULL, NULL },
       };
       luaL_newlib( L, foo );
    
       return 1;
    }
    

    测试:

    foo = require 'foo'
    
    for f in foo.list() do
       if f:bar() then
          print("success")
       else
          print("failed")
       end
       print(string.format( "%d: %s", f:id(), f:name()))
    end
    

    输出:

    success
    1: Foo 1
    success
    2: Foo 2
    failed
    3: Foo 3
    failed
    4: Foo 4
    success
    5: Foo 5
    failed
    6: Foo 6
    failed
    7: Foo 7
    failed
    8: Foo 8
    failed
    9: Foo 9
    failed
    10: Foo 10
    

    【讨论】:

    • foo_list 中,我将元表设置为 Foo 的一个实例。为什么那不是我想要返回的?如果这不是我想要返回的,那是什么?
    • 是的,但是在你的 iter 函数中你不返回那个实例,你只返回元表。
    • -2 行中的 lua_setmetatable( L, -2 ); 指向堆栈上的什么? lua_newuserdata()?
    • 是的,负堆栈索引从堆栈顶部向下计数。所以首先我们创建了 userdata 对象,所以它在栈顶(-1)。然后我们获取元表,将 it 放在栈顶,将 userdata 放在它下面 (-2)。
    • 我应该提到你已经这样做了。我所做的最重要的更改是创建元表的 __index 成员(没有它,所有对用户数据进行索引的尝试都会产生错误)并将其指向元表,以便将方法调用解析回元表本身。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-02-19
    • 1970-01-01
    • 1970-01-01
    • 2022-08-17
    • 1970-01-01
    • 1970-01-01
    • 2021-09-18
    相关资源
    最近更新 更多