Skip to content

Instantly share code, notes, and snippets.

@jzrake
Created April 17, 2012 20:10
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save jzrake/2408691 to your computer and use it in GitHub Desktop.
Save jzrake/2408691 to your computer and use it in GitHub Desktop.
Wrapping C++ classes with Lua
#include <iostream>
#include <string>
#include <sstream>
#include <map>
#ifdef __GNUC__
#include <cstdlib>
#include <cxxabi.h>
#endif // __GNUC__
extern "C" {
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
// ---------------------------------------------------------------------------
#define STACKDUMP { \
for (int i=1; i<=lua_gettop(L); ++i) { \
printf("%d: %s %s\n",i,luaL_typename(L,i),lua_tostring(L,i)); \
} \
} \
// ---------------------------------------------------------------------------
#define RETURN_ATTR_OR_CALL_SUPER(S) { \
AttributeMap::iterator m = attr.find(method_name); \
return m == attr.end() ? S::__getattr__(method_name) : m->second; \
} \
// ---------------------------------------------------------------------------
class LuaCppObject
{
protected:
typedef int (*LuaInstanceMethod)(lua_State *L);
typedef std::map<std::string, LuaInstanceMethod> AttributeMap;
// Used as the key into the symbol table (with weak values) of registered
// objects at global key `LuaCppObject`.
int __refid;
public:
// =======================================
// P U B L I C C L A S S M E T H O D S
// =======================================
LuaCppObject() : __refid(LUA_NOREF) { }
virtual ~LuaCppObject() { }
static void Init(lua_State *L)
// ---------------------------------------------------------------------------
// Set up the global table for LuaCppObject to have weak values, so that its
// entries are garbage collected. Call this once for each Lua instance.
// ---------------------------------------------------------------------------
{
lua_newtable(L);
lua_newtable(L);
lua_pushstring(L, "v");
lua_setfield(L, -2, "__mode");
lua_setmetatable(L, -2);
lua_setglobal(L, "LuaCppObject");
}
template <class T> static void Register(lua_State *L, int pos)
// ---------------------------------------------------------------------------
// Registers the constructor for a given class in the table at position `pos`.
// ---------------------------------------------------------------------------
{
pos = lua_absindex(L, pos);
lua_pushcfunction(L, T::new_lua_obj);
lua_setfield(L, pos, demangle(typeid(T).name()).c_str());
}
protected:
// =================================
// U T I L I T Y F U N C T I O N S
// =================================
template <class T> static T *checkarg(lua_State *L, int pos)
// ---------------------------------------------------------------------------
// This function first ensures that the argument at position `pos` is a valid
// user data. If so, it tries to dynamic_cast it to the template parameter
// `T`. This cast will fail if the object does not inherit from `T`, causing a
// graceful Lua error.
// ---------------------------------------------------------------------------
{
void *object_p = lua_touserdata(L, pos);
if (object_p == NULL) {
luaL_error(L, "invalid type");
}
LuaCppObject *cpp_object = *static_cast<LuaCppObject**>(object_p);
T *result = dynamic_cast<T*>(cpp_object);
if (result == NULL) {
luaL_error(L, "object of type '%s' is not a subtype of '%s'",
cpp_object->get_type().c_str(),
demangle(typeid(T).name()).c_str());
}
return result;
}
static void push_lua_obj(lua_State *L, LuaCppObject *object)
{
lua_getglobal(L, "LuaCppObject");
lua_rawgeti(L, -1, object->__refid);
lua_remove(L, -2);
}
static int make_lua_obj(lua_State *L, LuaCppObject *object)
{
LuaCppObject **place = (LuaCppObject**)
lua_newuserdata(L, sizeof(LuaCppObject*));
*place = object;
lua_newtable(L);
lua_pushcfunction(L, LuaCppObject::__index);
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, LuaCppObject::__tostring);
lua_setfield(L, -2, "__tostring");
lua_pushcfunction(L, LuaCppObject::__gc);
lua_setfield(L, -2, "__gc");
lua_setmetatable(L, -2);
// Register the object with a unique reference id for easy retrieval as a
// Lua object.
// -------------------------------------------------------------------------
lua_getglobal(L, "LuaCppObject");
lua_pushvalue(L, -2);
object->__refid = luaL_ref(L, -2);
lua_pop(L, 1);
return 1;
}
#ifdef __GNUC__
// ---------------------------------------------------------------------------
// Demangling names on gcc works like this:
// ---------------------------------------------------------------------------
static std::string demangle(const char *mname)
{
static int status;
char *realname = abi::__cxa_demangle(mname, 0, 0, &status);
std::string ret = realname;
free(realname);
return ret;
}
#else
static std::string demangle(const char *mname)
{
return std::string(mname);
}
#endif // __GNUC__
protected:
virtual std::string get_type()
// ---------------------------------------------------------------------------
// May be over-ridden by derived classes in case a different type name is
// desired. This function is called by the default `tostring` metamethod.
// ---------------------------------------------------------------------------
{
return demangle(typeid(*this).name());
}
virtual std::string tostring()
{
std::stringstream ss;
ss<<"<"<<this->get_type()<<" instance at "<<this<<">";
return ss.str();
}
// ===============================
// I N S T A N C E M E T H O D S
// ===============================
virtual LuaInstanceMethod __getattr__(std::string &method_name)
// ---------------------------------------------------------------------------
// The attributes below are inherited by all LuaCppObject's. If an attribute
// does not belong to a particular class instance, the super is invoked until
// we reach this function, at which point NULL is returned.
// ---------------------------------------------------------------------------
{
AttributeMap attr;
attr["get_refid"] = _get_refid_;
attr["get_type"] = _get_type_;
AttributeMap::iterator m = attr.find(method_name);
return m == attr.end() ? NULL : m->second;
}
static int _get_refid_(lua_State *L)
{
LuaCppObject *self = checkarg<LuaCppObject>(L, 1);
lua_pushnumber(L, self->__refid);
return 1;
}
static int _get_type_(lua_State *L)
{
LuaCppObject *self = checkarg<LuaCppObject>(L, 1);
lua_pushstring(L, self->get_type().c_str());
return 1;
}
// =====================
// M E T A M E T H O D S
// =====================
static int __index(lua_State *L)
// ---------------------------------------------------------------------------
// Arguments:
//
// (1) object: a user data pointing to a LuaCppObject
// (2) method_name: a string
//
// Returns: a static c-function which wraps the instance method
//
// ---------------------------------------------------------------------------
{
LuaCppObject *object = *static_cast<LuaCppObject**>(lua_touserdata(L, 1));
std::string method_name = lua_tostring(L, 2);
LuaInstanceMethod m = object->__getattr__(method_name);
if (m == NULL) {
luaL_error(L, "'%s' has no attribute '%s'", object->get_type().c_str(),
method_name.c_str());
}
lua_pushcfunction(L, m);
return 1;
}
static int __gc(lua_State *L)
// ---------------------------------------------------------------------------
// Arguments:
//
// (1) object: a user data pointing to a LuaCppObject
//
// Returns: nothing
// ---------------------------------------------------------------------------
{
LuaCppObject *object = *static_cast<LuaCppObject**>(lua_touserdata(L, 1));
// Unregister the object
// -------------------------------------------------------------------------
lua_getglobal(L, "LuaCppObject");
luaL_unref(L, -1, object->__refid);
lua_pop(L, 1);
// printf("killing object with refid %d...\n", object->__refid);
delete object;
return 0;
}
static int __tostring(lua_State *L)
{
LuaCppObject *object = *((LuaCppObject**) lua_touserdata(L, 1));
lua_pushstring(L, object->tostring().c_str());
return 1;
}
} ;
class Animal : public LuaCppObject
{
private:
std::string given_name;
public:
Animal() : given_name("noname") { }
virtual ~Animal() { }
virtual void speak() = 0;
virtual void eat(int number) = 0;
void set_name(const char *name) {
this->given_name = name;
}
std::string get_name() {
return this->given_name;
}
protected:
virtual LuaInstanceMethod __getattr__(std::string &method_name)
{
AttributeMap attr;
attr["speak"] = _speak_;
attr["eat"] = _eat_;
attr["get_name"] = _get_name_;
attr["set_name"] = _set_name_;
RETURN_ATTR_OR_CALL_SUPER(LuaCppObject);
}
static int _speak_(lua_State *L)
{
Animal *self = checkarg<Animal>(L, 1);
self->speak();
return 0;
}
static int _eat_(lua_State *L)
{
Animal *self = checkarg<Animal>(L, 1);
int n = luaL_checkinteger(L, 2);
self->eat(n);
return 0;
}
static int _get_name_(lua_State *L)
{
Animal *self = checkarg<Animal>(L, 1);
lua_pushstring(L, self->get_name().c_str());
return 1;
}
static int _set_name_(lua_State *L)
{
Animal *self = checkarg<Animal>(L, 1);
const char *name = luaL_checkstring(L, 2);
self->set_name(name);
return 0;
}
} ;
class Cat : public Animal
{
public:
void speak()
{
printf("meow!\n");
}
void eat(int number)
{
printf("eating %d fishes...\n", number);
}
void dig()
{
printf("digging\n");
}
protected:
virtual LuaInstanceMethod __getattr__(std::string &method_name)
{
AttributeMap attr;
attr["dig"] = _dig_;
RETURN_ATTR_OR_CALL_SUPER(Animal);
}
static int _dig_(lua_State *L)
{
Cat *self = checkarg<Cat>(L, 1);
self->dig();
return 0;
}
public:
static int new_lua_obj(lua_State *L)
{
return make_lua_obj(L, new Cat);
}
} ;
class Dog : public Animal
{
public:
void speak()
{
printf("bark!\n");
}
void eat(int number)
{
printf("eating %d rabbits...\n", number);
}
static int new_lua_obj(lua_State *L)
{
return make_lua_obj(L, new Dog);
}
} ;
class Poodle : public Dog
{
public:
void speak()
{
printf("bark!\n");
}
void eat(int number)
{
printf("eating %d rabbits (with a cute haircut)...\n", number);
}
static int new_lua_obj(lua_State *L)
{
return make_lua_obj(L, new Poodle);
}
} ;
class PetOwner : public LuaCppObject
{
private:
Dog *dog;
Cat *cat;
public:
void set_dog(Dog *_dog)
{
dog = _dog;
}
void set_cat(Cat *_cat)
{
cat = _cat;
}
protected:
virtual LuaInstanceMethod __getattr__(std::string &method_name)
{
AttributeMap attr;
attr["set_cat"] = _set_cat_;
attr["set_dog"] = _set_dog_;
attr["get_cat"] = _get_cat_;
attr["get_dog"] = _get_dog_;
RETURN_ATTR_OR_CALL_SUPER(LuaCppObject);
}
static int _set_dog_(lua_State *L) {
PetOwner *self = checkarg<PetOwner>(L, 1);
self->set_dog(checkarg<Dog>(L, 2));
return 0;
}
static int _set_cat_(lua_State *L) {
PetOwner *self = checkarg<PetOwner>(L, 1);
self->set_cat(checkarg<Cat>(L, 2));
return 0;
}
static int _get_dog_(lua_State *L) {
PetOwner *self = checkarg<PetOwner>(L, 1);
self->push_lua_obj(L, self->dog);
return 1;
}
static int _get_cat_(lua_State *L) {
PetOwner *self = checkarg<PetOwner>(L, 1);
self->push_lua_obj(L, self->cat);
return 1;
}
public:
static int new_lua_obj(lua_State *L)
{
return make_lua_obj(L, new PetOwner);
}
} ;
int main()
{
lua_State *L = luaL_newstate();
luaL_openlibs(L);
LuaCppObject::Init(L);
lua_newtable(L);
LuaCppObject::Register<Cat>(L, -1);
LuaCppObject::Register<Dog>(L, -1);
LuaCppObject::Register<Poodle>(L, -1);
LuaCppObject::Register<PetOwner>(L, -1);
lua_setglobal(L, "tests");
if (luaL_dofile(L, "run.lua")) {
printf("%s\n", lua_tostring(L, -1));
}
lua_close(L);
return 0;
}
LUAHOME = $(HOME)/Work/lunum/lua-5.2.0
CFLAGS = -Wall
default : luawrap
luawrap : luawrap.cpp
$(CXX) $(CFLAGS) -o $@ $^ -L$(LUAHOME)/lib -I$(LUAHOME)/include -llua
clean :
rm -f luawrap
local function test_method_calls()
local dog = tests.Dog()
local cat = tests.Cat()
for number, animal in pairs({dog, cat}) do
print("for animal type " .. animal:get_type())
animal:speak()
animal:eat(number)
end
local owner = tests.PetOwner()
cat:set_name("orange")
dog:set_name("murphy")
owner:set_dog(dog)
owner:set_cat(cat)
print(cat)
print("orange =?", owner:get_cat():get_name())
end
local function test_casting()
local dog = tests.Dog()
local cat = tests.Cat()
local jacko = tests.Poodle()
print(dog, cat, jacko)
local owner = tests.PetOwner()
owner:set_dog(dog)
print(owner:get_dog():eat(10))
owner:set_dog(jacko)
print(owner:get_dog():eat(10))
end
local function test_gc()
things = { }
for i=1,10 do
things[i] = tests.Dog()
end
print("should collect now...")
things[1] = nil
things[2] = nil
things[3] = nil
things[4] = nil
collectgarbage()
things[1] = tests.Dog()
things[2] = tests.Dog()
things[3] = tests.Dog()
things[4] = tests.Dog()
print("collected?")
for k,v in pairs(LuaCppObject) do
print(k,v)
end
end
test_casting()
test_method_calls()
--test_gc()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment