2012-08-23 76 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