Wednesday, February 18, 2015

DIY: Binding Lua5.3 with C++11

Let's bind C++ stuff to Lua5.3! :)

THE MEAT AND POTATOES


It might be enough to simple scripting for several projects.

int MesBox(lua_State *L)
{
const char* str = lua_tostring(L, 1);
MessageBoxA(nullptr, str, "LuaTest", MB_OK);
return 0;
}
void Bind()
{
lua_register(L, "MesBox", MesBox);
}
view raw mes_box.cpp hosted with ❤ by GitHub
MesBox("A message from Lua")
view raw mes_box.lua hosted with ❤ by GitHub

BRUTEFORCE WAY


Our main weapon is C++, so the above code doesn't satisfies me. So, let's bind C++ classes and create instances from Lua.
class MyClass
{
public:
int value;
};
static const char* myClassName = "MyClass";
static int MyClassSetValue(lua_State *L)
{
MyClass** pp = (MyClass**)luaL_checkudata(L, -2, myClassName);
(*pp)->value = (int)lua_tointeger(L, -1);
return 0;
}
static int MyClassGetValue(lua_State *L)
{
MyClass** pp = (MyClass**)luaL_checkudata(L, -1, myClassName);
lua_pushinteger(L, (*pp)->value);
return 1;
}
static int MyClassGC(lua_State *L)
{
MyClass** pp = (MyClass**)luaL_checkudata(L, -1, myClassName);
delete *pp;
return 0;
}
static int MyClassNew(lua_State *L)
{
MyClass** pp = (MyClass**)lua_newuserdata(L, sizeof(MyClass*));
*pp = new MyClass;
(*pp)->value = 0;
luaL_getmetatable(L, myClassName);
lua_setmetatable(L, -2);
return 1;
}
void BindMyClass()
{
int r = luaL_newmetatable(L, myClassName);
assert(r);
lua_pushstring(L, "__index");
lua_pushvalue(L, 1);
lua_settable(L, -3);
lua_pushstring(L, "__gc");
lua_pushcfunction(L, MyClassGC);
lua_settable(L, -3);
lua_pushstring(L, "SetValue");
lua_pushcfunction(L, MyClassSetValue);
lua_settable(L, -3);
lua_pushstring(L, "GetValue");
lua_pushcfunction(L, MyClassGetValue);
lua_settable(L, -3);
lua_pop(L, 1);
lua_register(L, "MyClass", MyClassNew);
}
view raw my_class.cpp hosted with ❤ by GitHub
local obj = MyClass()
obj:SetValue(1234)
print("My value is", obj:GetValue())
view raw my_class.lua hosted with ❤ by GitHub
My value is 1234
view raw output hosted with ❤ by GitHub

It works fantastic. Then MyClass is able to be generated with scripting, and the garbage collector calls our destructor and delete it.
However, it might be unpleasant chore to make additional method and classes. The bind code is too long and messy!

HACKING


Let's make stuff fun. The most brilliant thing for us is we are living in future. Now, C++11 allow us what we couldn't do before.

Below code is all to bind MyClass:

#include <new>
extern lua_State *L;
class MyClass
{
public:
MyClass()
{
value = 0;
puts("MyClass::MyClass called");
}
~MyClass()
{
puts("MyClass::~MyClass called");
}
int value;
};
static const char* myClassName = "MyClass";
#define GET_MYCLASS \
MyClass* p = (MyClass*)luaL_checkudata(L, 1, myClassName); \
if (!p) { return 0; }
void BindMyClass()
{
static struct luaL_Reg methods[] =
{
{ "__gc", [](lua_State* L) { GET_MYCLASS p->~MyClass(); return 0; } },
{ "SetValue", [](lua_State* L) { GET_MYCLASS p->value = (int)lua_tointeger(L, -1); return 0; } },
{ "GetValue", [](lua_State* L) { GET_MYCLASS lua_pushinteger(L, p->value); return 1; } },
{ nullptr, nullptr },
};
BindClass(L, myClassName, methods, [](lua_State* L) { new (lua_newuserdata(L, sizeof(MyClass))) MyClass; return 1; });
}

Most simple binds are able to be written with lambda. So, for compile it C++11 must be enabled. To prevent crashes by scripting bug, define a GET_MYCLASS macro to ensure the first element of the Lua stack is our MyClass. Note that MyClass instances are constructed with placement new. In this case, the destructor must be called directly instead of calling delete operator.

BindClass defined like below:

static int CreateCppClassInstance(lua_State* L)
{
const char* className = lua_tostring(L, lua_upvalueindex(1));
lua_CFunction actualInstanceCreator = lua_tocfunction(L, lua_upvalueindex(2));
actualInstanceCreator(L);
luaL_getmetatable(L, className);
lua_setmetatable(L, -2);
return 1;
}
static void CreateClassMetatable(lua_State* L, const char* className, luaL_Reg methods[])
{
luaL_newmetatable(L, className);
lua_pushstring(L, "__index");
lua_pushvalue(L, -2);
lua_settable(L, -3);
luaL_setfuncs(L, methods, 0);
lua_pop(L, 1);
}
static void CreateClassInstanceCreator(lua_State* L, const char* className, lua_CFunction creator)
{
lua_pushstring(L, className);
lua_pushcfunction(L, creator);
lua_pushcclosure(L, CreateCppClassInstance, 2);
lua_setglobal(L, className);
}
void BindClass(lua_State* L, const char* className, luaL_Reg methods[], lua_CFunction creator)
{
CreateClassMetatable(L, className, methods);
CreateClassInstanceCreator(L, className, creator);
}

As you can see, there is no MyClass dependencies. To avoid dependencies, I made a closer generator and put the class names and userdata creators into upvalue. Of course, BindClass can also be used for binding two or more extra classes.

CONCLUSION


Binding to Lua is not so complex and easy to learn. It's only combinations of small number of features such as stack and metatables. I saw programmers bothered increasing binding codes. I bet we can simplify them. Hope this helps to find your own solution.

No comments:

Post a Comment