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 4, 2022 11:18 pm GMT (diff)