【问题标题】:Getting the correct Lua metamethod to be invoked (C-api)获取要调用的正确 Lua 元方法(C-api)
【发布时间】:2012-03-22 02:34:02
【问题描述】:

我正在使用 [实际上正在学习使用] Lua C api。我是 Lua 的新手,如果我有一些不正确的术语,我深表歉意,如果有任何更正,我将不胜感激。

我有一个空的全局表 G,我在初始化时使用 lua_setglobal 创建了它。 G 的 __index 指向一个 C 函数,我相信它被称为元方法。调用时,此函数会创建一个新的 lightuserdata 项,并将其插入 G 全局表中。

所以,如果我的理解是正确的,G.foo 将导致调用 G 的 __index 元方法,并且 foo 将由它创建并添加到 G。未来对 G.foo 的调用将不再需要调用元方法,因为它会发现 foo 存在于 G 中。

现在,在创建 foo 时,我将元表关联到新创建的 lightuserdata (foo),方法是将其 __index 设置为 C 函数数组,其中最显着的是包含“set”和“get”。这个想法是,每当调用 foo:get() 时,都应该查找 foo 的元表以调用 C 函数来获取它的值,等等。

这是我看到的(正常)行为:

  • 从 lua 文件调用 G.foo。

    这会按预期使用 G 的元方法创建 foo。

  • 然后,调用 G.foo:get()

    由于 foo 已经是 G 的一部分(上一步),G 的元方法没有像预期的那样被调用。相反,检查 foo 的元表并调用对应于 'get' 的 C 函数。这也符合预期,并且正是我希望它工作的方式。

但是,如果我这样做:

  • 直接调用 G.foo:get() 无需先调用 G.foo

    然后,它调用 G 的元方法两次,一次用于 foo(预期),一次用于 'get'(非预期)。我不希望 'get' 被 G 的 __index 元方法处理。它基本上试图创建一个名为'get'的新lightuserdata(就像它为'foo'所做的那样)等等,这不是我想要做的。我希望查找新创建的 foo 的元表,以便为 foo 调用正确的“get”C 函数。

为了使问题最明显,我已经简化了我的用例,所以我希望它可以理解。另外,如果您能指出任何有助于我理解为什么会发生这种情况的 lua 文档或功能参考,我将不胜感激。

编辑: 添加一些带有相关部分的代码来演示我在做什么:

static void init()
{
    lua_newtable( luaVM );
    lua_createtable( luaVM, 0, 0 );             
    lua_pushcfunction( luaVM, lua_metaMethod );
    lua_setfield( luaVM, -2, "__index" );
    lua_setmetatable( luaVM, -2 );          
    lua_setglobal( luaVM, "G" );    
}

static const luaL_reg lua_methods[] =
{
    {   "set",      lua_set },
    {   "get",      lua_get },      
    {0, 0}
};

static int lua_metaMethod( lua_State *luaVM )
{
    // I get "foo" by using lua_tostring( luaVM, 2 ), and store that in 'name'.
    // I then lookup 'fooData', which is a pointer to data associated with foo that I want to add to G.

    lua_getglobal( "G" );
    lua_pushlightuserdata( luaVM, ( void* ) fooData );
    lua_createtable( luaVM, 0, 0 );
    lua_createtable( luaVM, 0, 0 );
    luaL_register( luaVM, NULL, lua_methods );              // Trying to make sure foo:get() calls one of these, etc.
    lua_setfield( luaVM, -2, "__index" );
    lua_setmetatable( luaVM, -2 );
    lua_setfield( luaVM, -2, name );

    return 1;
}

【问题讨论】:

  • Lua 是一个专有名称。这意味着第一个字母大写。所以它是 Lua,而不是 lua 或 LUA。
  • 在我原来的帖子中添加了一些代码来演示。看来我确实有术语错误,对不起!

标签: c lua


【解决方案1】:

鉴于您所描述的代码,问题在于lua_metaMethod(顺便说一句:将您的函数命名为lua_ 是一个错误的想法。这是为Lua API 函数保留的前缀)。

__index 元方法的返回值将返回给用户。所以如果用户说G.foo,那么G的__index元方法的返回值就会被返回。

lua_metaMethod 返回什么?它正好返回 1 个返回值。由于传递给函数的参数是堆栈上的第一个参数,因此它将返回第一个参数。 __index 元方法的第一个参数始终是调用元方法的表。在您的情况下,此表是存储在G 中的表。

因此,G.foo 将返回 G。因此,G.foo:get() 等价于G:get()(尽管第一个版本确实有一个额外的元方法调用)。我猜这不是你想要的 ;)

它应该返回的是刚刚存储在G["foo"]中的轻量级用户数据。

请考虑此代码。

static int lua_metaMethod( lua_State *luaVM )
{
    //We won't use this, so only do this if you need the string.
    const char *name = lua_tostring(luaVM, 2); 

    // I then lookup 'fooData', which is a pointer to data associated with foo that I want to add to G.

    //No need to get G. We already have it: the first parameter.
    lua_pushlightuserdata( luaVM, ( void* ) fooData );    //Stack: table, key, userdata
    lua_createtable( luaVM, 0, 0 );                       //Stack: table, key, userdata, {}
    lua_createtable( luaVM, 0, 0 );                       //Stack: table, key, userdata, {}, {}
    luaL_register( luaVM, NULL, lua_methods );              // Trying to make sure foo:get() calls one of these, etc.
    lua_setfield( luaVM, -2, "__index" );                 //Stack: table, key, userdata, {}
    lua_setmetatable( luaVM, -2 );                        //Stack: table, key, userdata

      //Stack now contains: table, key, userdata.
    lua_insert(luaVM, 2);                //Stack: table, userdata, key
    lua_pushvalue(luaVM, -2);            //Stack: table, userdata, key, userdata
    lua_rawset(luaVM, 1);            //Use rawset because table has a metatable. It's probably best not to invoke it.

      //Stack now contains: table, userdata.
    lua_insert(luaVM, 1);                 //Stack: userdata, table.

    return 1;  //Return just the userdata. `table` will be discarded.
}

【讨论】:

  • Nicol,感谢您将 Lua 堆栈转化为文字,以帮助我更好地可视化它。我将养成这样做的习惯以进行调试。我现在明白我正在返回 G,因此 :get() 将再次调用 G 的元方法。
  • 我已经在我的代码中删除了 lua_metaMethod 以支持非 lua_ 前缀名称,谢谢。另外,我现在让它完全按预期工作了!
猜你喜欢
  • 2014-01-21
  • 2021-06-13
  • 1970-01-01
  • 2011-03-08
  • 2017-02-26
  • 1970-01-01
  • 1970-01-01
  • 2013-10-03
  • 2018-02-11
相关资源
最近更新 更多