Lua:调用__index函数然后抛出错误

时间:2016-07-09 23:11:00

标签: lua

我正在尝试编写Lua绑定,以便可以在userdata上调用任意函数。我一直在努力的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。

1 个答案:

答案 0 :(得分:2)

__ 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代码将能够调用该函数。