Simpler Cpp Binding |
|
Here is the example from SimpleCppBinding again, but this time using the Lua 5.0 version of Luna [1].
Put the methods that you want to export from C++ to Lua in the
Account::methods
table.
The userdata index event will look for methods in the method table
whenever the obj:method(...)
syntax is used.
The method table is stored in the global variable named Account
so that Lua scripts can add methods written in Lua.
New Account objects can be created in a Lua script with either the
Account:new(...)
or Account(...)
syntax.
The latter is implemented using the call event for the method table.
Any C++ objects created by a Lua script will be deleted by the
userdata gc event.
The userdata metatable is hidden from the Lua script by setting
the __metatable
field to the method table.
Notice how getmetatable
returns the method table which is also
stored in the global variable Account
.
The method table has a metatable to make inheritance easier.
For Account
to inherit the methods of parent
,
set the index event for the method table like this:
getmetatable(Account).__index = parent
A [UML diagram] might help to visualize the table relationships. The reference composition links indicate a metatable relationship.
See CallingLuaFromCpp if you want to call Lua functions from your C++ code, and CppBindingWithLunar for an improved version of Luna 5.
extern "C" { #include "lua.h" #include "lauxlib.h" #include "lualib.h" } #include "luna.h" class Account { lua_Number m_balance; public: static const char className[]; static Luna<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"; #define method(class, name) {#name, &class::name} Luna<Account>::RegType Account::methods[] = { method(Account, deposit), method(Account, withdraw), method(Account, balance), {0,0} }; int main(int argc, char *argv[]) { lua_State *L = lua_open(); luaopen_base(L); luaopen_table(L); luaopen_io(L); luaopen_string(L); luaopen_math(L); luaopen_debug(L); Luna<Account>::Register(L); if(argc>1) lua_dofile(L, argv[1]); lua_setgcthreshold(L, 0); // collected garbage lua_close(L); return 0; }
extern "C" { #include "lua.h" #include "lauxlib.h" } template <typename T> class Luna { 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_pushstring(L, T::className); lua_pushvalue(L, methods); lua_settable(L, LUA_GLOBALSINDEX); lua_pushliteral(L, "__metatable"); lua_pushvalue(L, methods); lua_settable(L, metatable); // hide metatable from Lua getmetatable() lua_pushliteral(L, "__index"); lua_pushvalue(L, methods); lua_settable(L, metatable); lua_pushliteral(L, "__tostring"); lua_pushcfunction(L, tostring_T); lua_settable(L, metatable); lua_pushliteral(L, "__gc"); lua_pushcfunction(L, gc_T); lua_settable(L, metatable); lua_newtable(L); // mt for method table int mt = lua_gettop(L); lua_pushliteral(L, "__call"); lua_pushcfunction(L, new_T); lua_pushliteral(L, "new"); lua_pushvalue(L, -2); // dup new_T function lua_settable(L, methods); // add new_T to method table lua_settable(L, mt); // mt.__call = new_T lua_setmetatable(L, methods); // fill method table with methods from class T for (RegType *l = T::methods; l->name; l++) { /* edited by Snaily: shouldn't it be const RegType *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 } // 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 ud->pT; // pointer to T object } private: Luna(); // 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 userdataType *ud = static_cast<userdataType*>(lua_newuserdata(L, sizeof(userdataType))); ud->pT = obj; // store pointer to object in userdata luaL_getmetatable(L, T::className); // lookup metatable in Lua registry lua_setmetatable(L, -2); return 1; // userdata containing pointer to T object } // garbage collection metamethod static int gc_T(lua_State *L) { userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1)); T *obj = ud->pT; 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", obj); lua_pushfstring(L, "%s (%s)", T::className, buff); return 1; } };
This code can be compiled for Lua 5.0 as follows:
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)
If you want to pass an object to another C++ function,
you can use the check
function to verify that the userdata
is the right type, and get a pointer to the object.
In this example the C function rob
removes $20 from an Account.
extern "C" { #include "lua.h" #include "lauxlib.h" #include "lualib.h" } #include "luna.h" class Account { lua_Number m_balance; public: Account(double balance=0) : m_balance(balance) { } void deposit(double amount) { m_balance += amount; } void withdraw(double amount) { m_balance -= amount; } double balance(void) { return m_balance; } ~Account() { printf("deleted Account (%p)\n", this); } // Lua interface Account(lua_State *L) : m_balance(luaL_checknumber(L, 1)) { } int deposit (lua_State *L) { deposit (luaL_checknumber(L, 1)); return 0; } int withdraw(lua_State *L) { withdraw(luaL_checknumber(L, 1)); return 0; } int balance (lua_State *L) { lua_pushnumber(L, balance()); return 1; } static const char className[]; static Luna<Account>::RegType methods[]; }; const char Account::className[] = "Account"; #define method(class, name) {#name, &class::name} Luna<Account>::RegType Account::methods[] = { method(Account, deposit), method(Account, withdraw), method(Account, balance), {0,0} }; static int report (lua_State *L, int status) { if (status) { const char *msg = lua_tostring(L, -1); if (msg == NULL) msg = "(error with no message)"; fprintf(stderr, "ERROR: %s\n", msg); lua_pop(L, 1); } return status; } static int application (lua_State *L) { lua_settop(L, 0); lua_pushliteral(L, "_TRACEBACK"); lua_rawget(L, LUA_GLOBALSINDEX); // get traceback function int tb = lua_gettop(L); lua_pushliteral(L, "main"); lua_gettable(L, LUA_GLOBALSINDEX); report(L, lua_pcall(L, 0, 1, tb)); Account *a = Luna<Account>::check(L, -1); printf("the balance of 'a' is $%.2lf\n", a->balance()); return 0; } static int rob (lua_State *L) { Account *b = Luna<Account>::check(L, 1); b->withdraw(20.00); printf("take $20.00 from 'b'. the balance of 'b' is $%.2lf\n", b->balance()); return 1; } int main (int argc, char *argv[]) { lua_State *L = lua_open(); luaopen_base(L); luaopen_table(L); luaopen_io(L); luaopen_string(L); luaopen_math(L); luaopen_debug(L); Luna<Account>::Register(L); lua_register(L, "rob", rob); if (argc>1) { printf("loading '%s'\n", argv[1]); if (report(L, luaL_loadfile(L, argv[1]) || lua_pcall(L, 0, 0, 0)) == 0) { printf("running application\n"); if (report(L, lua_cpcall(L, &application, 0)) == 0) { printf("okay\n"); } } } lua_setgcthreshold(L, 0); // collected garbage lua_close(L); return 0; }
function printf(...) io.write(string.format(unpack(arg))) end function Account:show() printf("Account balance = $%0.02f\n", self:balance()) end b = Account(30) print('b =', b) b:show() rob(b) b:show() function main() a = Account(100) print('a =', a) a:show() a:deposit(50.30) a:show() return a end
$ ./test account.lua loading 'account4.lua' b = Account (0xa041d98) Account balance = $30.00 take $20.00 from 'b'. the balance of 'b' is $10.00 Account balance = $10.00 running application a = Account (0xa045390) Account balance = $100.00 Account balance = $150.30 the balance of 'a' is $150.30 okay deleted Account (0xa045390) deleted Account (0xa041d98)