Created
April 17, 2012 20:10
-
-
Save jzrake/2408691 to your computer and use it in GitHub Desktop.
Wrapping C++ classes with Lua
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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