Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extract C++ object pointer from Lua

Tags:

c++

lua

I have a class in C++ called "Point":

class Point
{
public:
  int x, y;

  //constructor
  Point(int x, int y)
  {
    this->x = x;
    this->y = y;
  }
};

My goal is to be able to instantiate a Point object with a Lua script, and extract the pointer to this object from the Lua stack.

Here is my (currently not working) attempt which hopefully clarifies what exactly I am trying to do; note that this code is essentially modified copy/paste from this tutorial and that I am using Lua 5.2:

static int newPoint(lua_State *L) 
{
    int n = lua_gettop(L);
    if (n != 2) 
       return luaL_error(L, "expected 2 args for Point.new()", n); 

    // Allocate memory for a pointer to object
    Point **p = (Point **)lua_newuserdata(L, sizeof(Point *));  

    double x = luaL_checknumber (L, 1);      
    double y = luaL_checknumber (L, 2); 

    //I want to access this pointer in C++ outside this function
    *p = new Point(x, y);

    luaL_getmetatable(L, "Point"); // Use global table 'Point' as metatable
    lua_setmetatable(L, -2); 

  return 1; 
}

static const luaL_Reg pointFuncs[] = {
  {"new", newPoint},
  {NULL, NULL}
};

//register Point to Lua
void registerPoint(lua_State *L)
{
    lua_createtable(L, 0, 0);
    // Register metatable for user data in registry
    luaL_newmetatable(L, "Point");
    luaL_setfuncs(L, pointFuncs, 0);           
    lua_pushvalue(L,-1);
    lua_setfield(L,-2, "__index");
    lua_setglobal(L, "Point");
}

Point* checkPoint(lua_State* L, int index)
{
  void* ud = 0;
  luaL_checktype(L, index, LUA_TTABLE); 
  lua_getfield(L, index, "__index");
  ud = luaL_checkudata(L, index, "Point");;  

  return *((Point**)ud);     
 }

 int main()
 {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
        registerPoint(L);
    luaL_dofile(L, "testz.lua");
    lua_getglobal(L, "Point");
    Point *foo = checkPoint(L, lua_gettop(L));
        std::cout << "x: " << foo->x << " y: " << foo->y;
    lua_close(L);
    return 0;
 }

And here is the Lua script:

local point = Point.new(10,20)

Running this code, I get the following error: "bad argument #3 (Point expected, got table)" at the line: ud = luaL_checkudata(L, index, "Point") inside the checkPoint() function.

If any one could steer me in the right direction, it would be much appreciated.

like image 463
user2687268 Avatar asked Aug 15 '13 21:08

user2687268


Video Answer


2 Answers

There are a couple of issues in your above usage and binding.

Inside your testing script, local point will not be seen by your host c++ program. This is because, well, it's local to that script. If you want it accessible from c++, either return point as a value from the script or make it global and retrieve it using lua_getglobal(L, "point").

The checkPoint function has some unnecessary code. If you look at the other luaL_check* functions provided by the Lua C API, they mainly check if the given value at the stack index is the right type. If it is then convert that type over to something C++ can use.

So in your 'checkPoint' function all you really need to do is:

Point* checkPoint(lua_State* L, int index)
{
  return *((Point **) luaL_checkudata(L, index, "Point"));
}

If you change your script to this, for example:

local point = Point.new(10,20)
return point

you should be able to access point from C++ in the following way:

// ...
luaL_dofile(L, "testz.lua");

Point *foo = checkPoint(L, -1);
std::cout << "x: " << foo->x << " y: " << foo->y;
// ...

Another important point is regarding C++ object lifetime when exposed to lua. Because you're allocating and constructing with the C++ 'new' operator whenever Point.new is called from the script, you are indirectly saying let lua handle this exposed C++ object's lifetime. That means you'll also want to implement a __gc metamethod to act as a 'finalizer' or non-deterministic destructor. Without this you would have no way to 'delete' the point object and reclaim the space when lua garbage collects the corresponding userdata.

To do this you can augment your code as follows:

  • Write a deletePoint function that gets called by lua on userdata gc.

    static int deletePoint(lua_State *L)
    {
      Pointer **p = checkPoint(L, 1);
      delete *p;
      *p = NULL;  // probably not needed but to be safe
      return 0;
    }
    
  • Add that to your pointFuncs so lua knows about it.

    static const luaL_Reg pointFuncs[] =
    {
      {"new", newPoint},
      {"__gc", deletePoint},
      {NULL, NULL}
    };
    
like image 120
greatwolf Avatar answered Oct 22 '22 08:10

greatwolf


I'd recommend not writing your own binding, but rather use a tested and documented one, such as luabridge or luabind

Your binding would be reduced to:

getGlobalNamespace (L)
    .beginClass<Point>("Point")
     .addConstructor<void (*) (int,int)>()
     .addData("X", &Point::x)
     .addData("Y", &Point::y)
    .endClass()
;

and your lua would look like

local point = Point(10,20)

with luabridge.

For extracting values, see for example LuaRef in luabridge. If you use c++11 there are some other quite beautiful lua binding libraries.

like image 23
Dmitry Ledentsov Avatar answered Oct 22 '22 08:10

Dmitry Ledentsov