简体   繁体   中英

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. 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):

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

I realize there are wrappers that may do this, but I would prefer to understand the underlying Lua mechanism before implementing any wrappers.

You're returning a metatable from your iterator, not a foo instance.

More importantly, your metatable contains methods, but no 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.

I'd recommend learning how metatables work in Lua, very well, before implementing the same via the C API.

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:

  1. If __index is a function, that function will be called with a string id and foo.id resolves to whatever that function returns.
  2. If __index is a table, the value stored in rawget(__index, 'id') so foo.id essentially resolve to `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 .

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 .


Here's a modified, simplified version of your code which shows the __index metamethod working:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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