9

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.

2 Answers 2

9

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}
    };
    
Sign up to request clarification or add additional context in comments.

3 Comments

@user2687268 I've added a note about handling userdata clean.
that's a good explanation, but it seems to assume that the garbage collector will not run before the point pointer gets the point from the lua state. This might be not the case with this simple example, but in more elaborate scripts you'll not know when gc will delete your point. Hence, the whole fuss of memory lifetime management in binding libraries.
p.s. one of the typical solution is wrapping your object in a reference-counted object, so that the garbage collector call only frees memory when there are no references on the c++ side, and vice-versa, if you delete the wrapper object on the c++ side, if the original object is still alive as userdata, the memory should be freed by the garbage collector.
6

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.

2 Comments

I will definitely check out luabind. As a learning experience I do wish to get this working however. Unfortunately I still get the same error as before even after I switched to the colon operator in my Lua code and changed the argument check in newPoint() function. Would you happen to know why this is?
updated my answer as id didn't have an actual explanation, and greatwolf did explain

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.