Simpler Cpp Binding


Description

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.

account.cc C++ code

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;
}

luna.h for Lua 5.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;
 }
};

Compiling the Code

This code can be compiled for Lua 5.0 as follows:

g++ -o test account.cc -L/usr/local/lib -llua -llualib

Lua Test Code

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 Code Output

$ ./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)

Passing Objects

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;
}

Lua Test Code

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 Code Output

$ ./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)

See Also


RecentChanges · preferences
edit · history
Last edited May 4, 2022 5:18 pm GMT (diff)

AltStyle によって変換されたページ (->オリジナル) /