获取正确的Lua元方法被调用(C API)

我正在使用[实际学习使用]Lua C API。我对Lua很新,如果我有一些术语不正确,我很抱歉,并希望得到任何更正。

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

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

现在,当创建foo时,我通过将其__index设置为包含C函数数组的元表来关联新创建的lightuserdata(foo),其中包含“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调用正确的C函数“get”。

为了使问题更加明显,我简化了我的用例,因此希望它足够清楚。此外,如果您可以指出任何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;
}

原文链接 https://stackoverflow.com/questions/9815658

点赞
stackoverflow用户734069
stackoverflow用户734069

根据你描述的代码,问题出现在 lua_metaMethod 中(顺便说一句:使用 lua_ 命名函数不是个好主意。这个前缀已经被保留给 Lua API 函数了)。

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

lua_metaMethod 返回了一个返回值。因为传递给函数的参数是栈上的第一个,所以它将返回第一个参数。__index 元方法的第一个参数总是元方法所在的表。在你的代码中,这个表就是存储在 G 中的表。

因此,G.foo 将返回 **G**。因此,G.foo:get() 等同于 G:get()(尽管第一种方式多了一个元方法调用)。我猜这不是你想要的 ;)

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

考虑使用这段代码代替:

static int lua_metaMethod( lua_State *luaVM )
{
    //这里我们不用这个值,所以只在需要字符串时才获取。
    const char *name = lua_tostring(luaVM, 2);

    // 我们查找 'fooData',这是一个指针,指向与 foo 相关的数据,我们要将其添加到 G 中。

    //我们不需要再次获取 G。我们已经有了它:第一个参数。
    lua_pushlightuserdata( luaVM, ( void* ) fooData );    //栈:table, key, userdata
    lua_createtable( luaVM, 0, 0 );                       //栈:table, key, userdata, {}
    lua_createtable( luaVM, 0, 0 );                       //栈:table, key, userdata, {}, {}
    luaL_register( luaVM, NULL, lua_methods );              // 确保 foo:get() 调用了这些函数之一等等。
    lua_setfield( luaVM, -2, "__index" );                 //栈:table, key, userdata, {}
    lua_setmetatable( luaVM, -2 );                        //栈:table, key, userdata

      //栈现在包含:table, key, userdata。
    lua_insert(luaVM, 2);                //栈:table, userdata, key
    lua_pushvalue(luaVM, -2);            //栈:table, userdata, key, userdata
    lua_rawset(luaVM, 1);            //使用 rawset 是因为表有元表。最好不要调用它。

      //栈现在包含:table, userdata。
    lua_insert(luaVM, 1);                 //栈:userdata, table。

    return 1;  //只返回 userdata。`table` 将被丢弃。
}
2012-03-22 04:05:57