I'm trying to write Lua bindings so that one can call arbitrary functions on a userdata. An MCV example I've been working on is below.
In summary: we have the C function newarray
pushed to a table in the Lua globals so that one can create a new array object. Suppose that the array is a database record. I have two kinds of operation that I want to perform on it after generating it with newarray
(for this bad example): accessing an element, and destroying the object.
Since I don't know how many elements there will be (in a real world example), I decide to make __index
a function and use an if-statement to determine if the function was "destroy" or anything else (i.e. "give me this element"). If it was "destroy", delete the object; otherwise, return the requested element.
#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;
}
I compiled with:
gcc -std=c99 -Wall -fPIC -shared -o TestArray.so test.c -llua
The Lua test program is as follows:
require("TestArray")
a = TestArray.newarray()
print(a[5])
a:destroy()
The output:
$ 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]: ?
$
So Lua does what it's supposed to by retrieving the 6th element's value (in terms of C) and printing it (as it surely does through indexFunc
). Then it proceeds to execute the destroy-specific code in indexFunc
, then tries to look for a function called destroy
, and I have no idea why. It found the __index
metamethod, so I don't understand why it looked elsewhere afterwards. Why does it do this, and what am I doing wrong?
Lua version: 5.1.4.
__index
metamethod should only retrieve the value for the key "destroy", that is,indexFunc
must only return a value (function "destroy") without executing this function. Destructor should be implemented as separate functionint destroyFunc(lua_State* L)
. Fast solution: just replacea:destroy()
withlocal _ = a.destroy
:-) – Egor Skriptunoffdestroy
which basically mean "invoke the result of retrieving 'destroy' as a function"? If you'd like to submit it as an answer I will accept, because AFAIK you don't get any well-deserved rep from a comment. :) – Doddy