2012-08-23 44 views
3

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

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机制。

回答

2

您正在从您的迭代器中返回metatable,而不是foo实例。

更重要的是,您的metatable包含方法,但没有metamethods。特别是,如果您想foo方法调用来解析元数据中的方法,则需要设置__index元方法。

我建议在通过C API实现相同功能之前,先了解Lua中metatables的工作原理。

当你说foo.id,如果id不存在foo(或foo是用户数据)和foo已与__index集的元表,这将解析为以下两种情况之一:

  1. 如果__index是一个函数,该函数将被调用一个字符串idfoo.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 
+0

在' foo_list'我将metatable设置为成为Foo的一个实例。为什么这不是我想要返回的?如果这不是我想要回报的那么那是什么? – PaulH

+0

是的,但在你的ITER功能你不回该实例,你只返回元表。 – Mud

+0

什么的'-2'在线路'lua_setmetatable(L -2,);'指向在堆栈上? lua_newuserdata()? – PaulH