Reputation: 7863
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.
Upvotes: 3
Views: 1649
Reputation: 29000
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:
__index
is a function, that function will be called with a string id
and foo.id
resolves to whatever that function returns.__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
Upvotes: 3