如何在 Lua C API 中获取由 Lua 设置的元表

Lua:

a = {
    b = "c",
    d = {
        e = "f",
        g = "h"
    }
}
setmetatable(a.d, {__ismt = true})

cfun(a) --调用 C 函数遍历表 a

C:

int cfun(lua_State *L)
{
    lua_pushnil(L);
    while (lua_next(L, -2) != 0)
    {
        // 遍历表

        lua_pop(L, 1);
    }
}

当主机客户端遍历表时,如何知道是否有元表?然后如何获取元表?

点赞
用户2816703
用户2816703

表格是以树的形式展现的,需要采用迭代模式遍历树。Lua已经有了堆栈实现,这让工作变得更容易。

  • 进入时,堆栈顶部有一个表格,你将推入一个 nil 元素,因为 lua_next() 会在检查表格之前从堆栈中消耗一个元素。因此,堆栈将看起来像 table -> nil
  • 接下来,我们调用 lua_next(),它将从堆栈中消耗一个元素,将两个新的键-值对添加到表格中。堆栈将看起来像 table -> key -> value。如果没有下一个元素,调用的返回值为0。
  • 如果返回值是1,并且堆栈上的值是嵌套表格,则将 nil 推入堆栈,所以现在堆栈将看起来像 table -> key -> table -> nil。现在你几乎像是在开始一样,所以通过循环,你将开始遍历嵌套表格。
  • 如果返回值为1,并且值不是表格,则对该值进行操作
  • 如果返回值为0,我们可以检查这是否是元表。在检查之后,你将弹出值并检查堆栈是否为 table -> keyany -> key。如果堆栈上的第二个元素不是表格,则已经完成遍历,并且将中断循环。

下面是实现算法的 C 代码。我将 printf 留在代码中以帮助调试,但实际使用时应该将其删除。

static int cfun(lua_State *L)
{
    luaL_checktype(L, 1, LUA_TTABLE);
    lua_pushnil(L);        // 为第一个 lua_next 增加额外的空间
    int loop=1;
    do {
        if ( lua_next(L,-2) != 0 ) {
            if (lua_istable(L,-1)) {
                printf("Table [%s] \n", lua_tostring(L, -2));
                lua_pushnil(L); // 开始遍历这个子表
            } else {
                // 键和值都在堆栈上。我们可以得到它们的类型
                printf("(%s - %s)\n",
                    lua_tostring(L, -2),
                    lua_typename(L, lua_type(L, -1)));
                lua_pop(L,1);
            }
        } else {
            printf("table finished, still on stack (%s -> %s -> %s)\n",
                    lua_typename(L, lua_type(L, -3)),
                    lua_typename(L, lua_type(L, -2)),
                    lua_typename(L, lua_type(L, -1)));
            if (lua_getmetatable(L,-1)) {
                // 表格有元表。现在元表在堆栈上了
                printf("Metatable detected\n");
                lua_pop(L,1); // 从堆栈中删除元表
            }
            lua_pop(L,1); // 将当前表格从堆栈中弹出
            if (!lua_istable(L, -2)) {
                loop = 0; // 没有更多的表格在堆栈中,中断循环
            }
        }
    } while (loop);
    lua_pop(L,1); // 清除最后一个元素
    lua_pushnumber(L,0); // 返回0
    return 1;
}
2021-03-17 16:03:26