Calling Lua From Cpp |
|
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.
/*
==============================================================================
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;
}
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;
}
};
This code can be compiled for Lua 5.0 as follows:
g++ -o test player.cc -L/usr/local/lib -llua -llualib
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 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)