使用 Lunar 进行 Cpp 绑定 |
|
这是 Luna 的改进版本(参见 SimplerCppBinding),它允许您从 C++ 应用程序调用 Lua 方法。
您也可以在 Lua 脚本或 C++ 应用程序中创建对象。在 Lua 中创建的对象将由 userdata 的 gc 事件删除。您可以指定在 C++ 应用程序中创建的对象是否由 userdata 的 gc 事件删除。
lunar.h
有两个额外的公共方法:push
和 call
。
push
接收指向对象的指针,并将一个唯一的 [1] userdata 推送到堆栈上,并返回此 userdata 的堆栈索引。可选的第二个参数指定在 userdata 的 gc 事件发生时是否删除对象。此参数的默认值为 false,这意味着不会删除对象。
call
只是 lua_pcall
的包装器。在调用 call
之前,将 userdata 和参数推送到堆栈上。然后,它将从 userdata 中检索命名方法,并使用 lua_pcall
调用此函数。call
将返回堆栈上的结果数量,或者如果发生错误则返回负数。任何错误消息都将保留在堆栈顶部。
CallingLuaFromCpp 展示了如何使用这些额外功能。
extern "C" { #include "lua.h" #include "lauxlib.h" #include "lualib.h" } #include "lunar.h" class Account { lua_Number m_balance; public: static const char className[]; static Lunar<Account>::RegType methods[]; Account(lua_State *L) { m_balance = luaL_checknumber(L, 1); } int deposit (lua_State *L) { m_balance += luaL_checknumber(L, 1); return 0; } int withdraw(lua_State *L) { m_balance -= luaL_checknumber(L, 1); return 0; } int balance (lua_State *L) { lua_pushnumber(L, m_balance); return 1; } ~Account() { printf("deleted Account (%p)\n", this); } }; const char Account::className[] = "Account"; Lunar<Account>::RegType Account::methods[] = { LUNAR_DECLARE_METHOD(Account, deposit), LUNAR_DECLARE_METHOD(Account, withdraw), LUNAR_DECLARE_METHOD(Account, balance), {0,0} }; int main(int argc, char *argv[]) { lua_State *L = lua_open(); luaL_openlibs(L); Lunar<Account>::Register(L); if(argc>1) luaL_dofile(L, argv[1]); lua_gc(L, LUA_GCCOLLECT, 0); // collected garbage lua_close(L); return 0; }
extern "C" { #include "lua.h" #include "lauxlib.h" } template <typename T> class Lunar { typedef struct { T *pT; } userdataType; public: typedef int (T::*mfp)(lua_State *L); typedef struct { const char *name; mfp mfunc; } RegType; static void Register(lua_State *L) { lua_newtable(L); int methods = lua_gettop(L); luaL_newmetatable(L, T::className); int metatable = lua_gettop(L); // store method table in globals so that // scripts can add functions written in Lua. lua_pushvalue(L, methods); set(L, LUA_GLOBALSINDEX, T::className); // hide metatable from Lua getmetatable() lua_pushvalue(L, methods); set(L, metatable, "__metatable"); lua_pushvalue(L, methods); set(L, metatable, "__index"); lua_pushcfunction(L, tostring_T); set(L, metatable, "__tostring"); lua_pushcfunction(L, gc_T); set(L, metatable, "__gc"); lua_newtable(L); // mt for method table lua_pushcfunction(L, new_T); lua_pushvalue(L, -1); // dup new_T function set(L, methods, "new"); // add new_T to method table set(L, -3, "__call"); // mt.__call = new_T lua_setmetatable(L, methods); // fill method table with methods from class T for (RegType *l = T::methods; l->name; l++) { lua_pushstring(L, l->name); lua_pushlightuserdata(L, (void*)l); lua_pushcclosure(L, thunk, 1); lua_settable(L, methods); } lua_pop(L, 2); // drop metatable and method table } // call named lua method from userdata method table static int call(lua_State *L, const char *method, int nargs=0, int nresults=LUA_MULTRET, int errfunc=0) { int base = lua_gettop(L) - nargs; // userdata index if (!luaL_checkudata(L, base, T::className)) { lua_settop(L, base-1); // drop userdata and args lua_pushfstring(L, "not a valid %s userdata", T::className); return -1; } lua_pushstring(L, method); // method name lua_gettable(L, base); // get method from userdata if (lua_isnil(L, -1)) { // no method? lua_settop(L, base-1); // drop userdata and args lua_pushfstring(L, "%s missing method '%s'", T::className, method); return -1; } lua_insert(L, base); // put method under userdata, args int status = lua_pcall(L, 1+nargs, nresults, errfunc); // call method if (status) { const char *msg = lua_tostring(L, -1); if (msg == NULL) msg = "(error with no message)"; lua_pushfstring(L, "%s:%s status = %d\n%s", T::className, method, status, msg); lua_remove(L, base); // remove old message return -1; } return lua_gettop(L) - base + 1; // number of results } // push onto the Lua stack a userdata containing a pointer to T object static int push(lua_State *L, T *obj, bool gc=false) { if (!obj) { lua_pushnil(L); return 0; } luaL_getmetatable(L, T::className); // lookup metatable in Lua registry if (lua_isnil(L, -1)) luaL_error(L, "%s missing metatable", T::className); int mt = lua_gettop(L); subtable(L, mt, "userdata", "v"); userdataType *ud = static_cast<userdataType*>(pushuserdata(L, obj, sizeof(userdataType))); if (ud) { ud->pT = obj; // store pointer to object in userdata lua_pushvalue(L, mt); lua_setmetatable(L, -2); if (gc == false) { lua_checkstack(L, 3); subtable(L, mt, "do not trash", "k"); lua_pushvalue(L, -2); lua_pushboolean(L, 1); lua_settable(L, -3); lua_pop(L, 1); } } lua_replace(L, mt); lua_settop(L, mt); return mt; // index of userdata containing pointer to T object } // get userdata from Lua stack and return pointer to T object static T *check(lua_State *L, int narg) { userdataType *ud = static_cast<userdataType*>(luaL_checkudata(L, narg, T::className)); if(!ud) { luaL_typerror(L, narg, T::className); return NULL; } return ud->pT; // pointer to T object } private: Lunar(); // hide default constructor // member function dispatcher static int thunk(lua_State *L) { // stack has userdata, followed by method args T *obj = check(L, 1); // get 'self', or if you prefer, 'this' lua_remove(L, 1); // remove self so member function args start at index 1 // get member function from upvalue RegType *l = static_cast<RegType*>(lua_touserdata(L, lua_upvalueindex(1))); return (obj->*(l->mfunc))(L); // call member function } // create a new T object and // push onto the Lua stack a userdata containing a pointer to T object static int new_T(lua_State *L) { lua_remove(L, 1); // use classname:new(), instead of classname.new() T *obj = new T(L); // call constructor for T objects push(L, obj, true); // gc_T will delete this object return 1; // userdata containing pointer to T object } // garbage collection metamethod static int gc_T(lua_State *L) { if (luaL_getmetafield(L, 1, "do not trash")) { lua_pushvalue(L, 1); // dup userdata lua_gettable(L, -2); if (!lua_isnil(L, -1)) return 0; // do not delete object } userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1)); T *obj = ud->pT; if (obj) delete obj; // call destructor for T objects return 0; } static int tostring_T (lua_State *L) { char buff[32]; userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1)); T *obj = ud->pT; sprintf(buff, "%p", (void*)obj); lua_pushfstring(L, "%s (%s)", T::className, buff); return 1; } static void set(lua_State *L, int table_index, const char *key) { lua_pushstring(L, key); lua_insert(L, -2); // swap value and key lua_settable(L, table_index); } static void weaktable(lua_State *L, const char *mode) { lua_newtable(L); lua_pushvalue(L, -1); // table is its own metatable lua_setmetatable(L, -2); lua_pushliteral(L, "__mode"); lua_pushstring(L, mode); lua_settable(L, -3); // metatable.__mode = mode } static void subtable(lua_State *L, int tindex, const char *name, const char *mode) { lua_pushstring(L, name); lua_gettable(L, tindex); if (lua_isnil(L, -1)) { lua_pop(L, 1); lua_checkstack(L, 3); weaktable(L, mode); lua_pushstring(L, name); lua_pushvalue(L, -2); lua_settable(L, tindex); } } static void *pushuserdata(lua_State *L, void *key, size_t sz) { void *ud = 0; lua_pushlightuserdata(L, key); lua_gettable(L, -2); // lookup[key] if (lua_isnil(L, -1)) { lua_pop(L, 1); // drop nil lua_checkstack(L, 3); ud = lua_newuserdata(L, sz); // create new userdata lua_pushlightuserdata(L, key); lua_pushvalue(L, -2); // dup userdata lua_settable(L, -4); // lookup[key] = userdata } return ud; } }; #define LUNAR_DECLARE_METHOD(Class, Name) {#Name, &Class::Name}
此代码可以为 Lua 5.0 编译如下
g++ -o test account.cc -L/usr/local/lib -llua -llualib
function printf(...) io.write(string.format(unpack(arg))) end function Account:show() printf("Account balance = $%0.02f\n", self:balance()) end a = Account(100) b = Account:new(30) print('a =', a) print('b =', b) print('metatable =', getmetatable(a)) print('Account =', Account) table.foreach(Account, print) a:show() a:deposit(50.30) a:show() a:withdraw(25.10) a:show() parent = {} function parent:rob(amount) amount = amount or self:balance() self:withdraw(amount) return amount end getmetatable(Account).__index = parent debug.debug()
$ ./test account.lua a = Account (0xa041d98) b = Account (0xa045390) metatable = table: 0xa044f28 Account = table: 0xa044f28 show function: 0xa046760 balance function: 0xa0455f8 withdraw function: 0xa045300 deposit function: 0xa045508 new function: 0xa044fe8 Account balance = $100.00 Account balance = $150.30 Account balance = $125.20 lua_debug> a:show() Account balance = $125.20 lua_debug> b:show() Account balance = $30.00 lua_debug> print(a:rob(20)) 20 lua_debug> a:show() Account balance = $105.20 lua_debug> b:deposit(a:rob()) lua_debug> a:show() Account balance = $0.00 lua_debug> b:show() Account balance = $135.20 lua_debug> cont deleted Account (0xa045390) deleted Account (0xa041d98)