Calling Lua From Cpp

lua-users home
wiki

Showing revision 8

Description

Luna[1] is good for calling C++ functions from Lua. If you wish to call Lua functions from C++, then you'll need to stash your Lua function in a subtable of either the registry or the table of globals, and then use lua_pcall to invoke it from your C++ code.

This example uses an revised version of Luna that puts the registered functions in a method table that is accessible from the table of globals so that scripts can add methods written in Lua. Put the methods that you want to export to from C++ to Lua in the Player::methods table.

The newindex event handler function has a lookup table with a list of all the members that can be set. Put the members you want to set from Lua in Player::setters.

The index event handler function has a lookup table with a list of all the members that you can get. It tries this table first, and if the index is not a member you can get, then it tries looking in the table of methods. Put the members you want to get from Lua in Player::getters.

Objects can be created in Lua with either the Player(...) or Player:new(...) syntax. The first is implimented with a call event on the method table. Any C++ objects created by Lua will be deleted by the gc event.

You can get inheritance by setting the index event for the method table like this: getmetatable(Player).__index = parent.

To call Lua methods from C++, use: Lunar<Player>::call(...).

If you are an OO purist and you don't need to get/set memebers, then see CppBindingWithLunar.

player.cc C++ code

/*
==============================================================================
  Example C++ binding with members and methods
==============================================================================
*/

#include <stdio.h>
#include <string.h>

extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}

#include "lunar.h"

class Player {
  int    id;
  char   name[16];
  int    age;
  int    health;
  long   ammo;
  double x,y;
  static int id_counter;

public:

  Player(char n[16], int a, int h, long am, double xx, double yy)
    : age(a), health(h), ammo(am), x(xx), y(yy)
    { strcpy(name, n); id = ++id_counter; }
    

  Player (lua_State *L) {
    size_t name_len;
    const char *name_str = luaL_checklstring(L, 1, &name_len);
    if( name_len > 15 ) luaL_error(L, "name too long"); /* die */
    strcpy(name, name_str);
    age    = luaL_checkint(L, 2);
    health = luaL_checkint(L, 3);
    ammo   = luaL_checklong(L, 4);
    x      = luaL_checknumber(L, 5);
    y      = luaL_checknumber(L, 6);
    id = ++id_counter;
  }
  
  ~Player (void) {
    printf("Goodbye (%p) %d:%s at (%lf,%lf)\n", this, id, name, x, y);
  }
  
  int getid(lua_State *L) { lua_pushnumber(L, id); return 1; }
  int getname(lua_State *L) { lua_pushstring(L, name); return 1; }
  int getage(lua_State *L) { lua_pushnumber(L, age); return 1; }
  int gethealth(lua_State *L) { lua_pushnumber(L, health); return 1; }
  int sethealth(lua_State *L) { health += luaL_checkint(L, 1); return 0; }

  int getammo(lua_State *L) { lua_pushnumber(L, ammo); return 1; }
  int setammo(lua_State *L) { ammo += luaL_checkint(L, 1); return 0; }

  int getx(lua_State *L) { lua_pushnumber(L, x); return 1; }
  int setx(lua_State *L) { x = luaL_checknumber(L, 1); return 0; }
  int gety(lua_State *L) { lua_pushnumber(L, y); return 1; }
  int sety(lua_State *L) { y = luaL_checknumber(L, 1); return 0; }

  int position (lua_State *L) {
    double oldx = x;
    double oldy = y;
    if( lua_gettop(L) > 1 ) {
      x = luaL_checknumber(L, 1);
      y = luaL_checknumber(L, 2);
    }
    lua_pushnumber(L, oldx);
    lua_pushnumber(L, oldy);
    return 2;
  }

  int test (lua_State *L) {
    int n = luaL_checkint(L, 1);
    lua_pushstring(L, name);
    lua_pushnumber(L, id);
    lua_pushnumber(L, ammo);
    lua_pushnumber(L, x);
    lua_pushnumber(L, y);
    return n;
  }

  static const char className[];
  static Lunar<Player>::RegType methods[];
  static Lunar<Player>::RegType getters[];
  static Lunar<Player>::RegType setters[];
};

const char Player::className[] = "Player";
int Player::id_counter = 0;

#define method(class, name) {#name, &class::name}

Lunar<Player>::RegType Player::methods[] = {
  method(Player, position),
  method(Player, test),
  {0,0}
};

#define member(class, name, method) {#name, &class::method}

Lunar<Player>::RegType Player::getters[] = {
  member(Player, id,     getid),
  member(Player, name,   getname),
  member(Player, age,    getage),
  member(Player, health, gethealth),
  member(Player, ammo,   getammo),
  member(Player, x,      getx),
  member(Player, y,      gety),
  {0,0}
};

Lunar<Player>::RegType Player::setters[] = {
  member(Player, health, sethealth),
  member(Player, ammo,   setammo),
  member(Player, x,      setx),
  member(Player, y,      sety),
  {0,0}
};


static int app (lua_State *L)
{
  Player hero("bob", 12, 100, 1000, 0,0);

  lua_pushliteral(L, "hero");
  Lunar<Player>::push(L, &hero);
  lua_settable(L, LUA_GLOBALSINDEX);

  lua_pushliteral(L, "health");
  int n = Lunar<Player>::call(L, &hero, "fun", 1);  // hero:fun('health')
  printf("n = %d\n", n);
  if (n < 0) {
    fprintf(stderr, "ERROR: %s\n", lua_tostring(L, -1));
  } else {
    if (n>0) printf("score: %d\n", luaL_checkint(L, -1));
  }
  printf("game over\n");

  return 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);

  Lunar<Player>::Register(L);

  if(argc>1) lua_dofile(L, argv[1]);

  printf("\ndone init\n\nnow running application\n");

  int status = lua_cpcall(L, &app, 0);
  if (status) {
    const char *msg = lua_tostring(L, -1);
    if (msg == NULL) msg = "(error with no message)";
    fprintf(stderr, "ERROR: %s\n", msg);
  }

  lua_close(L);
  return 0;
}

lunar.h for Lua 5.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 const struct rt { 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_pushcfunction(L, tostring_T);
    set(L, metatable, "__tostring");

    lua_newtable(L);
    addmembers(L, T::getters); // fill with getters from class T
    lua_pushvalue(L, methods);
    addmethods(L, thunk, T::methods); // fill with methods from class T
    lua_pushcclosure(L, index_handler, 2);
    set(L, metatable, "__index");

    lua_newtable(L);
    addmembers(L, T::setters); // fill with setters from class T
    lua_pushcclosure(L, newindex_handler, 1);
    set(L, metatable, "__newindex");

    deletetable(L);
    lua_pushvalue(L, -1);           // dup
    lua_pushcclosure(L, gc_T, 1);   // delete table is an upvalue of gc_T
    set(L, metatable, "__gc");
    lua_pushcclosure(L, new_T, 1);  // delete table is an upvalue of new_T
    lua_pushvalue(L, -1);           // dup new_T function
    set(L, methods, "new");         // add new_T to method table
    lua_newtable(L);                // mt for method table
    lua_insert(L, -2);              // swap
    set(L, -3, "__call");           // mt.__call = new_T
    lua_setmetatable(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, T *obj, const char *method,
                  int nargs=0, int nresults=LUA_MULTRET)
  {
    push(L, obj);                      // userdata containing pointer to T obj
    int base = lua_gettop(L) - nargs;  // function index
    lua_insert(L, base);               // put userdata under args

    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

    lua_pushliteral(L, "_TRACEBACK");
    lua_rawget(L, LUA_GLOBALSINDEX);   // get traceback function
    lua_insert(L, base);               // put it under method, userdata, args

    int status = lua_pcall(L, 1+nargs, nresults, base);  // call method
    lua_remove(L, base);               // remove traceback function
    if (status) {
      const char *msg = lua_tostring(L, -1);
      if (!msg) msg = "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) {
    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
  }

  // get userdata from Lua stack and return pointer to T object
  static T *check(lua_State *L, int narg) {
    luaL_checktype(L, narg, LUA_TUSERDATA);
    userdataType *ud =
      static_cast<userdataType*>(luaL_checkudata(L, narg, T::className));
    if(!ud) luaL_typerror(L, narg, T::className);
    return ud->pT;
  }

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);             // userdata containing pointer to T obj

    // store userdata in delete table so that gc_T will delete it
    lua_pushvalue(L, -1);     // dup userdata to use as key in delete table
    lua_pushboolean(L, 1);    // true value
    lua_rawset(L, lua_upvalueindex(1));  // store in delete table

    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;
    lua_pushvalue(L, -1);  // dup userdata to use as key in delete table
    lua_rawget(L, lua_upvalueindex(1));  // try lookup in delete table
    if (lua_toboolean(L, -1)) {  // check if userdata was created by new_T
      delete obj;  // call destructor for T objects
      // printf("deleted (%p)\n", obj);
    }
    return 0;
  }

  static int deletetable(lua_State *L) {
    lua_newtable(L);  // delete table has userdata created by new_T
    int deltable = lua_gettop(L);
    lua_pushvalue(L, deltable);  // delete table is its own metatable
    lua_setmetatable(L, deltable);
    lua_pushliteral(L, "__mode");
    lua_pushliteral(L, "k");
    lua_rawset(L, deltable);  // delete table.__mode = "k"
    return deltable;
  }

  static int 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 int addmethods (lua_State *L, lua_CFunction func, RegType *l) {
    for (; l->name; l++) {
      lua_pushstring(L, l->name);
      lua_pushlightuserdata(L, (void*)l);
      lua_pushcclosure(L, func, 1);
      lua_settable(L, -3);
    }
  }

  static void addmembers (lua_State *L, RegType *l) {
    for (; l->name; l++) {
      lua_pushstring(L, l->name);
      lua_pushlightuserdata(L, (void*)l);
      lua_settable(L, -3);
    }
  }

  static int index_handler (lua_State *L) {
    // stack has userdata, index
    lua_pushvalue(L, 2);                     // dup index
    lua_rawget(L, lua_upvalueindex(1));      // lookup member by name
    if (!lua_islightuserdata(L, -1)) {
      lua_pop(L, 1);                         // drop nil value
      lua_pushvalue(L, 2);                   // dup index
      lua_gettable(L, lua_upvalueindex(2));  // else try methods
      if (lua_isnil(L, -1))                  // invalid member
        luaL_error(L, "cannot get member '%s'", lua_tostring(L, 2));
      return 1;
    }
    return xet_member(L);                    // call get function
  }

  static int newindex_handler (lua_State *L) {
    // stack has userdata, index, value
    lua_pushvalue(L, 2);                     // dup index
    lua_rawget(L, lua_upvalueindex(1));      // lookup member by name
    if (!lua_islightuserdata(L, -1))         // invalid member
      luaL_error(L, "cannot set member '%s'", lua_tostring(L, 2));
    return xet_member(L);                    // call set function
  }

  static int xet_member (lua_State *L) {
    RegType *l = static_cast<RegType*>(lua_touserdata(L, -1));
    lua_pop(L, 1);     // drop lightuserdata
    userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1));
    T *obj = ud->pT;
    lua_remove(L, 2);  // remove index
    lua_remove(L, 1);  // remove userdata
    return (obj->*(l->mfunc))(L);  // call get function
  }

  static int tostring_T (lua_State *L)
  {
    char buff[sizeof(T::className)+24];
    userdataType *ud = static_cast<userdataType*>(lua_touserdata(L, 1));
    T *obj = ud->pT;
    sprintf(buff, "%s (%p)", T::className, obj);
    lua_pushstring(L, buff);
    return 1;
  }
};

Compiling the Code

This code can be compiled for Lua 5.0 as follows:

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

Lua Test Code

print('= Player =', Player)
for n,v in pairs(Player) do print(n,v) end
for n,v in pairs(getmetatable(Player)) do print(n,v) end

g = Player('good', 99, 100, 0,    34,65)
b = Player('bad',  44, 100, 1000, 16.7,19.25)

local base = {}
getmetatable(Player).__index = base

base.member_names = { 'id', 'name', 'age', 'health', 'ammo', 'x', 'y' }

function base:dump()
  for _,n in self.member_names do print(n, self[n]) end
end

print('*** g ***', g)
g:dump()

print('*** b ***', b)
b:dump()

print('b.name =', b.name, 'pos = ', b.x, b.y)
b.ammo = 1000000
b.x = 40
b.y = 50

print'*** modified b ***'
b:dump()

score = 0

function Player:fun(s)
  print('fun', s)
  debug.debug()
  return score
end

Test Code Output

$ ./test player.lua
= Player =      table: 0xa044f28
test    function: 0xa045478
new     function: 0xa045738
position        function: 0xa045430
__call  function: 0xa045738
*** g ***       Player (0xa0471b8)
id      1
name    good
age     99
health  100
ammo    0
x       34
y       65
*** b ***       Player (0xa045320)
id      2
name    bad
age     44
health  100
ammo    1000
x       16.7
y       19.25
b.name =        bad     pos =   16.7    19.25
*** modified b ***
id      2
name    bad
age     44
health  100
ammo    1001000
x       40
y       50

done init

now running application
fun     health
lua_debug> print(hero)
Player (0x22fca0)
lua_debug> print(hero.id, hero.age, hero:position())
3       12      0       0
lua_debug> hero.x = 45
lua_debug> hero.y = -10
lua_debug> hero.age = 13
[string "hero.age = 13..."]:1: cannot set member 'age'
lua_debug> hero.ammo = 10000
lua_debug> hero:dump()
id      3
name    bob
age     12
health  100
ammo    11000
x       45
y       -10
lua_debug> cont
n = 1
score: 0
game over
Goodbye (0x22fca0) 3:bob at (45.000000,-10.000000)
Goodbye (0xa045320) 2:bad at (40.000000,50.000000)
Goodbye (0xa0471b8) 1:good at (34.000000,65.000000)


RecentChanges · preferences
edit · history · current revision
Edited May 18, 2003 2:40 pm GMT (diff)