Peter
Peter

Reputation: 3145

Accessing C++ class state via Lua wrapper yields garbage values

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

Answers (1)

Darius
Darius

Reputation: 1180

There are 2 problems:

  • You allocating memory auto **__obj in lua function new, but acessing as auto *__obj in lua function foo_get_state.
  • Memory leak - you're missing functionality to collect garbage for 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

Related Questions