简体   繁体   English

如何让Lua迭代器返回一个C结构?

[英]How to have a Lua iterator return a C struct?

I have a Visual Studio 2008 C++03 project using Lua 5.2.1 where I would like to have an iterator return an object such that I can get parameter values or call related functions. 我有一个使用Lua 5.2.1的Visual Studio 2008 C ++ 03项目,我希望迭代器返回一个对象,以便我可以获取参数值或调用相关函数。 For example: 例如:

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

I am using the following C++ code to implement this (error checking omitted): 我使用以下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;
}

But, when I run this, I get the Lua error: foo.lua:2: calling 'bar' on bad self 但是,当我运行这个时,我得到了Lua错误: foo.lua:2: calling 'bar' on bad self

I realize there are wrappers that may do this, but I would prefer to understand the underlying Lua mechanism before implementing any wrappers. 我意识到有些包装器可以做到这一点,但我更愿意在实现任何包装器之前理解底层的Lua机制。

You're returning a metatable from your iterator, not a foo instance. 你从迭代器返回metatable,而不是foo实例。

More importantly, your metatable contains methods, but no metamethods. 更重要的是,你的metatable包含方法,但没有metamethods。 In particular, if you want foo method calls to resolve to the methods in your metatable, you'll need to set the __index metamethod. 特别是,如果您希望foo方法调用解析为metatable中的方法,则需要设置__index元方法。

I'd recommend learning how metatables work in Lua, very well, before implementing the same via the C API. 我建议在通过C API实现相同功能之前,先了解meta在Lua中的工作原理。

When you say foo.id , if id doesn't exist in foo (or foo is a userdata) and foo has a metatable with __index set, this will resolve to one of two things: 当你说foo.id ,如果foo中不存在id (或者foo是用户数据)并且foo具有设置了__index的metatable,这将解析为以下两种情况之一:

  1. If __index is a function, that function will be called with a string id and foo.id resolves to whatever that function returns. 如果__index是一个函数,那么将使用字符串id调用该函数,并且foo.id将解析为该函数返回的任何内容。
  2. If __index is a table, the value stored in rawget(__index, 'id') so foo.id essentially resolve to `rawget(getmetatable(foo).__index, 'id'). 如果__index是一个表,那么存储在rawget(__index, 'id')使得foo.id基本上解析为`rawget(getmetatable(foo).__ index,'id')。

So if you want to use foo:id() , you can create a generic id method foo's metatable that returns the value self.id . 因此,如果你想使用foo:id() ,你可以创建一个通用的id方法foo的metatable,它返回值self.id

If you want to use foo.id , you either need to change foo to a table and store id as part of it's state, or implement __index as a function where you do string compares and recognize that id should resolve to self.id . 如果你想使用foo.id ,你需要将foo更改为一个表并将id作为其状态的一部分进行存储,或者将__index实现为一个函数,在这里你进行字符串比较并识别id应解析为self.id


Here's a modified, simplified version of your code which shows the __index metamethod working: 这是代码的修改后的简化版本,它显示了__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;
}

A test: 一个测试:

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

Output: 输出:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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