Reputation: 3145
I'm trying to expose a simple C++ class to Lua via Lua's C API. I know there are libraries that already solve this problem but I would like to understand how to do this with just the C API.
Here is my program:
#define LUA_LIB
#include <cassert>
extern "C"
{
#include "lua.h"
#include "lauxlib.h"
}
namespace
{
class Foo
{
public:
Foo(int state)
: _state(state)
{}
int get_state() const
{ return _state; }
private:
int _state;
};
int foo_new(lua_State *L)
{
assert(lua_gettop(L) == 2);
luaL_checktype(L, 1, LUA_TTABLE);
lua_newtable(L);
lua_pushvalue(L, 1);
lua_setmetatable(L, -2);
lua_pushvalue(L, 1);
lua_setfield(L, 1, "__index");
int state = lua_tointeger(L, 2);
auto **__obj = static_cast<Foo **>(lua_newuserdata(L, sizeof(Foo *)));
*__obj = new Foo(state);
lua_setfield(L, -2, "__self");
return 1;
}
int foo_get_state(lua_State *L)
{
assert(lua_gettop(L) == 1);
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "__self");
auto *__obj = static_cast<Foo *>(lua_touserdata(L, -1));
lua_pushinteger(L, __obj->get_state());
return 1;
}
luaL_Reg const foo_functions[] = {
{"new", foo_new},
{"get_state", foo_get_state},
{nullptr, nullptr}
};
} // namespace
extern "C"
{
LUALIB_API int luaopen_foo(lua_State *L)
{
luaL_newlib(L, foo_functions);
lua_setglobal(L, "Foo");
return 1;
}
} // extern "C"
foo_new
creates a Lua table which stores a C++ Foo
object as userdata in its __self
field. I call this function from Lua as local foo = Foo:new(1)
. That seems to work, however, subsequently calling print(foo:get_state())
does not return 1
but some garbage value. Why is that and how can I fix it?
Upvotes: 0
Views: 190
Reputation: 1180
There are 2 problems:
auto **__obj
in lua function new
, but acessing as auto *__obj
in lua function foo_get_state
.new Foo()
It is not good to mix metatable with data object, but in your case it should work like:
int foo_new(lua_State *L)
{
assert(lua_gettop(L) == 2);
luaL_checktype(L, 1, LUA_TTABLE);
lua_newtable(L);
lua_pushvalue(L, 1);
lua_setmetatable(L, -2);
lua_pushvalue(L, 1);
lua_setfield(L, 1, "__index");
int state = lua_tointeger(L, 2);
auto *__obj = static_cast<Foo *>(lua_newuserdata(L, sizeof(Foo)));
new(__obj) Foo(state);
lua_setfield(L, -2, "__self");
return 1;
}
int foo_get_state(lua_State *L)
{
assert(lua_gettop(L) == 1);
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "__self");
auto *__obj = static_cast<Foo *>(lua_touserdata(L, -1));
assert(__obj != nullptr);
lua_pushinteger(L, __obj->get_state());
return 1;
}
int foo_gc(lua_State *L)
{
assert(lua_gettop(L) == 1);
luaL_checktype(L, 1, LUA_TTABLE);
lua_getfield(L, 1, "__self");
auto *__obj = static_cast<Foo *>(lua_touserdata(L, -1));
if (__obj)
__obj->~Foo();
lua_pushnil(L);
lua_setfield(L, -3, "__self");
return 0;
}
luaL_Reg const foo_functions[] = {
{"new", foo_new},
{"get_state", foo_get_state},
{"__gc", foo_gc},
{nullptr, nullptr}
};
__gc
is not necessary if your work with simple data without memory/handles allocations as lua garbage collection frees allocated memory.
Upvotes: 1