Simpler Cpp Binding

lua-users home
wiki

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 15, 2008 3:50 am GMT (diff)