【问题标题】:Lua: calling __index function works then throws errorLua:调用 __index 函数有效然后抛出错误
【发布时间】:2016-07-09 23:10:59
【问题描述】:

我正在尝试编写 Lua 绑定,以便可以在用户数据上调用任意函数。下面是我一直在研究的一个 MCV 示例。

总而言之:我们将 C 函数 newarray 推送到 Lua 全局变量中的一个表中,这样就可以创建一个新的数组对象。假设数组是一个数据库记录。在使用newarray 生成它之后,我有两种操作要对其执行(对于这个不好的例子):访问元素和销毁对象。

由于我不知道会有多少元素(在现实世界的示例中),我决定将__index 设为一个函数并使用 if 语句来确定该函数是“销毁”还是其他什么(即“给我这个元素”)。如果是“destroy”,则删除该对象;否则,返回请求的元素。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#define TEST_METATABLE "_test_mt"

typedef struct
{
    int* array;
} array_t;

int newArray(lua_State* L)
{
    assert(lua_gettop(L) == 0);
    array_t* array = lua_newuserdata(L, sizeof(array_t));
    array->array = malloc(sizeof(int) * 10);

    for (int i = 0; i < 10; i++)
        array->array[i] = i;

    /* Set metatable */

    lua_getfield(L, LUA_REGISTRYINDEX, TEST_METATABLE);
    lua_setmetatable(L, -2);

    return 1;
}

int indexFunc(lua_State* L)
{
    int argc = lua_gettop(L);
    array_t* array = luaL_checkudata(L, 1, TEST_METATABLE);
    const char* key = luaL_checkstring(L, 2);
    int ret = 0;

    if (!strcmp(key, "destroy"))
    {
        if (argc != 2)
        {
            lua_settop(L, 0);
            luaL_error(L, "Invalid arguments");
        }

        if (array->array)
        {
            free(array->array);
            array->array = NULL;
        }

        printf("Finished destroy\n");

        lua_settop(L, 0);
    }
    else
    {
        if (argc != 2)
        {
            lua_settop(L, 0);
            luaL_error(L, "Invalid arguments");
        }

        if (lua_tointeger(L, 2))
        {
            lua_pushinteger(L, array->array[lua_tointeger(L, 2)]);
        }
        else
        {
            lua_settop(L, 0);
            luaL_error(L, "Bad index supplied");
        }

        lua_remove(L, 2);
        lua_remove(L, 1);
        ret = 1;
    }

    return ret;
}

int luaopen_TestArray(lua_State* L)
{
    /* Set up metatable */

    lua_newtable(L);

    lua_pushliteral(L, "__index");
    lua_pushcfunction(L, indexFunc);
    lua_settable(L, -3);

    lua_setfield(L, LUA_REGISTRYINDEX, TEST_METATABLE);

    /* Set up 'static' stuff */

    lua_newtable(L);

    lua_pushliteral(L, "newarray");
    lua_pushcfunction(L, newArray);
    lua_settable(L, -3);

    lua_setglobal(L, "TestArray");

    return 0;
}

我编译的是:

gcc -std=c99 -Wall -fPIC -shared -o TestArray.so test.c -llua

Lua测试程序如下:

require("TestArray")

a = TestArray.newarray()

print(a[5])

a:destroy()

输出:

$ lua test.lua
5
Finished destroy
lua: test.lua:7: attempt to call method 'destroy' (a nil value)
stack traceback:
        test.lua:7: in main chunk
        [C]: ?
$

所以 Lua 通过检索第 6 个元素的值(以 C 表示)并打印它(通过indexFunc 肯定会这样做)来完成它应该做的事情。然后它继续执行indexFunc 中的特定于销毁的代码,然后 试图寻找一个名为destroy 的函数,我不知道为什么。它找到了__index 元方法,所以我不明白为什么它后来在别处寻找。为什么会这样,我做错了什么?

Lua 版本:5.1.4。

【问题讨论】:

  • __index 元方法应该只检索键“destroy”的值,也就是说,indexFunc 必须只返回一个值(函数“destroy”)而不执行此函数。析构函数应该实现为单独的函数int destroyFunc(lua_State* L)。快速解决方案:只需将a:destroy() 替换为local _ = a.destroy :-)
  • @EgorSkriptunoff 哦,我明白了,所以这是因为destroy 后面的括号基本上意味着“调用检索'destroy'作为函数的结果”?如果您想将其作为答案提交,我会接受,因为 AFAIK 您不会从评论中获得任何当之无愧的代表。 :)

标签: lua


【解决方案1】:

__index 应该返回一个值。你的没有。

具体来说,当你写这个时:

a:destroy()

相当于:

getmetatable(a).__index(a, "destroy")(a)

即调用 __index 元方法,然后调用它返回的任何内容,并将其传递给 a 作为参数。

但是,如果我们查看您的 __index 实现,它不尊重该合同:

int indexFunc(lua_State* L)
{
  int argc = lua_gettop(L);
  array_t* array = luaL_checkudata(L, 1, TEST_METATABLE);
  const char* key = luaL_checkstring(L, 2);
  int ret = 0;

  if (!strcmp(key, "destroy"))
  {
    /* ... delete the array ... */
    lua_settop(L, 0);
  }
  else
  {
    /* ... push the value ... */
  }

  return ret; /* since key == "destroy", ret == 0 here */
}

如果键是"destroy",则不返回函数;相反,它立即销毁数组并且不返回任何内容,在这种情况下相当于返回 nil。然后 lua 代码尝试调用返回的 nil 并爆炸。

相反,您需要创建一个单独的函数来执行销毁,例如

int destroyFunc(lua_State * L) {
  array_t array = luaL_checkudata(L, 1, TEST_METATABLE);
  free(array->array);
  array->array = NULL;
  return 0;
}

然后让你的 __index 返回那个函数而不是调用它:

lua_pushcfunction(L, destroyFunc);
return 1;

此时 Lua 代码将能够调用该函数。

【讨论】:

    猜你喜欢
    • 2016-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多