THE MEAT AND POTATOES
It might be enough to simple scripting for several projects.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MesBox("A message from Lua") |
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
local obj = MyClass() | |
obj:SetValue(1234) | |
print("My value is", obj:GetValue()) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
My value is 1234 |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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