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.
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}
};
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With