Rough, work-in-progress, code for a C++ wrapper I'm using for some of my code. This wrapper considerably simplifies the amount of work needed to create a Lua wrapper around C++ classes. It was inspired, and is in fact still substantially derived from, Lenny Palozzi's "luna.h" header.
// C++ interface wrapper for Lua
// 16/02/2001 jcw@equi4.com
#include <assert.h>
extern "C" {
#include <lua.h>
#include <lauxlib.h>
}
class Lua {
lua_State* _state;
int _pos;
int _res;
public:
Lua (lua_State* L, int n_) : _state (L), _pos (n_), _res (0) {}
bool more() const { return _pos < lua_gettop(_state); }
void done() const { assert(!more()); }
Lua& operator++ () { assert(more()); ++_pos; return *this; }
int nextarg() { assert(more()); return ++_pos; }
operator lua_State* () const { return _state; }
int numresults() const { return _res; }
// required args
operator bool () const { return !lua_isnil(_state, _pos); }
operator int () const { return luaL_check_int(_state, _pos); }
operator long () const { return luaL_check_long(_state, _pos); }
operator double () const { return luaL_check_number(_state, _pos); }
operator char () const { return *luaL_check_string(_state, _pos); }
operator const char* () const { return luaL_check_string(_state, _pos); }
// optional args
Lua& operator>> (bool& a) { if (more()) a = ++*this; return *this; }
Lua& operator>> (int& a) { if (more()) a = ++*this; return *this; }
Lua& operator>> (long& a) { if (more()) a = ++*this; return *this; }
Lua& operator>> (double& a) { if (more()) a = ++*this; return *this; }
Lua& operator>> (char& a) { if (more()) a = ++*this; return *this; }
Lua& operator>> (const char*& a) { if (more()) a = ++*this; return *this; }
// results
Lua& operator<< (bool a) {
if (a)
lua_pushnumber(_state, 1);
else
lua_pushnil(_state);
++_res;
return *this;
}
Lua& operator<< (int a) {
lua_pushnumber(_state, a);
++_res;
return *this;
}
Lua& operator<< (long a) {
lua_pushnumber(_state, a);
++_res;
return *this;
}
Lua& operator<< (double a) {
lua_pushnumber(_state, a);
++_res;
return *this;
}
Lua& operator<< (char a) {
lua_pushlstring(_state, &a, 1);
++_res;
return *this;
}
Lua& operator<< (const char* a) {
lua_pushstring(_state, a);
++_res;
return *this;
}
Lua& pushusertag(void* a, int t) {
lua_pushusertag(_state, a, t);
++_res;
return *this;
}
};
// Inspired by the luna.h code of Lenny Palozzi - lenny.palozzi@home.com
template <class T> class Luna {
/* constructs T objects */
static int constructor(lua_State* L) {
Lua lua (L, 1);
(void) create(lua);
return lua.numresults();
}
/* member function dispatcher */
static int proxy(lua_State* L) {
assert(lua_tag(L, 1) == otag);
T* obj = static_cast<T*>(lua_touserdata(L,1));
int i = static_cast<int>(lua_tonumber(L,-1));
lua_pop(L, 1);
Lua lua (L, 1);
(obj->*(T::regTable[i].mfunc))(lua);
return lua.numresults();
}
/* method call dispatcher */
static int methodcall(lua_State* L) {
assert(lua_tag(L, 1) == otag); // table or userdata
assert(lua_istable(L, -1) && lua_tag(L, -1) == otag);
assert(lua_isstring(L, 2));
lua_pushvalue(L,2);
lua_rawget(L, -2);
return 1;
}
/* member access dispatcher */
static int getmember(lua_State* L) {
//assert(lua_isuserdata(L, 1) && lua_tag(L, 1) == otag);
assert(lua_tag(L, 1) == otag); // table or userdata
assert(lua_istable(L, -1) && lua_tag(L, -1) == otag);
assert(lua_isstring(L, 2));
lua_pushvalue(L,2);
lua_rawget(L, -2);
lua_pushvalue(L,1);
lua_call(L, 1, 1);
return 1;
}
/* releases objects */
static int gc_obj(lua_State* L) {
T* obj = static_cast<T*>(lua_touserdata(L, -1));
delete obj;
return 0;
}
protected:
Luna(); /* hide default constructor */
public:
static int otag;
/* member function map */
struct RegType { const char* name; void(T::*mfunc)(Lua&); };
/* register class T */
static void Register(lua_State* L, int data =0) {
lua_newtable(L);
if (otag == 0) {
otag = lua_newtag(L);
// TODO: there's a major mixup in here - the table should only
// get the function tm, and the constructed userdata objects
// should get the gc and gettable tm's, using a 2nd tag number
lua_pushcfunction(L, &Luna<T>::constructor);
lua_settagmethod(L, otag, "function");
lua_pushcfunction(L, &Luna<T>::gc_obj);
lua_settagmethod(L, otag, "gc");
lua_pushvalue(L,-1);
lua_pushcclosure(L, data ? &Luna<T>::getmember
: &Luna<T>::methodcall, 1);
lua_settagmethod(L, otag, "gettable");
}
lua_settag(L,otag);
/* register the member functions */
for (int i=0; T::regTable[i].name; i++) {
lua_pushstring(L, T::regTable[i].name);
lua_pushnumber(L, i);
lua_pushcclosure(L, &Luna<T>::proxy, 1);
lua_settable(L, -3);
}
lua_setglobal(L, T::className);
}
/* constructs T objects and returns them for use in C++ */
static T* create(Lua& lua) {
T* obj = new T(lua); /* new T */
assert(otag != 0);
lua.pushusertag(obj,otag);
return obj; /* also leaves userdata object on Lua stack */
}
/* grab an arg of the proper type */
static T* getarg(Lua& lua, int n = 0) {
if (n == 0) n = lua.nextarg();
void* p = lua_touserdata(lua, n);
return p != 0 && lua_tag(lua, n) == otag ? (T*) p : 0;
}
};
template <class T> int Luna<T>::otag = 0;
template <class T> Lua& operator>> (Lua& lua, T*& arg) {
if (lua.more()) arg = Luna<T>::getarg(lua);
return lua;
}
Here is a very early example of use:
class MkProperty : public c4_Property
{
public:
MkProperty(Lua& lua) {
char type = ++lua;
const char* name = ++lua;
*(c4_Property*) this = c4_Property (type, name);
}
void name(Lua& lua) { lua << Name(); }
void type(Lua& lua) { lua << Type(); }
void id(Lua& lua) { lua << GetId(); }
static const char className[];
static const Luna<MkProperty>::RegType regTable[];
};
const char MkProperty::className[] = "MkProperty";
const Luna<MkProperty>::RegType MkProperty::regTable[] = {
{"name", &MkProperty::name},
{"type", &MkProperty::type},
{"id", &MkProperty::id},
{0}
};
[...]
class MkStorage : public c4_Storage
{
public:
MkStorage(Lua& lua) {
const char* n = ++lua;
int f = 0; lua >> f;
*(c4_Storage*) this = c4_Storage (n, f);
}
void view(Lua& lua) { *MkView::pushnew(lua) = View(++lua); }
void getas(Lua& lua) { *MkView::pushnew(lua) = GetAs(++lua); }
void autocommit(Lua& lua) { AutoCommit(); }
void commit(Lua& lua) { int n = 0; lua >> n; lua << Commit(n); }
void rollback(Lua& lua) { int n = 0; lua >> n; lua << Rollback(n); }
void contents(Lua& lua) { *MkView::pushnew(lua) = *this; }
void aside(Lua& lua) {
MkStorage* a = Luna<MkStorage>::getarg(lua); assert(a != 0);
SetAside(*a);
}
void description(Lua& lua) {
const char* n = 0; lua >> n;
lua << Description(n);
}
static const char className[];
static const Luna<MkStorage>::RegType regTable[];
};
const char MkStorage::className[] = "MkStorage";
const Luna<MkStorage>::RegType MkStorage::regTable[] = {
{"view", &MkStorage::view},
{"getas", &MkStorage::getas},
{"autocommit", &MkStorage::autocommit},
{"commit", &MkStorage::commit},
{"rollback", &MkStorage::rollback},
{"contents", &MkStorage::contents},
{"aside", &MkStorage::aside},
{"description", &MkStorage::description},
{0}
};
[...]
extern "C" int mk4lua_open(lua_State *L);
int mk4lua_open(lua_State *L)
{
Luna<MkProperty>::Register(L, 1); // access as fields, not functions
Luna<MkRowRef>::Register(L);
Luna<MkView>::Register(L);
Luna<MkStorage>::Register(L);
return 0;
}
The above leads to a wrapper which can be used as follows from Lua:
p=MkProperty('S','address')
print(p.name)
s=MkStorage('filename')
v=s.getas('info[zipcode:S,address:S]')
[...]
s.commit()
This example is incomplete, but it may give you an idea of how the wrapper mechanism works.
JC, it looks like the user would be able to call class:func() without an object. In Register you register the class name and functions. You'll probably get a GPF somewhere. You could probably use the tag values to distinguish a proper call from an illegal one.
-Lenny
Thanks, good point, I hadn't thought of that and will look into in --jcw
JC, you have the following "hack" in your constructor *(c4_Storage*) this = c4_Storage (n, f); how does it work? My test shows that the base default constructor is called, then the non default is called. I am wrapping some classes with my template class and am finding that the base class must have a default constructor; I cannot pull the values out of Lua and pass them to the base constructor in the constructor initializer list. Thanks. -Lenny
This hack won't help you in that respect - it too relies on existing contructor behavior. This cast triggers the "c4_Storage::operator=" (which can be defaulted), even though MkStorage is a derived class. I have been careful to allow assignments of my c4_Storage base class to work - not that hard in this case, since c4_Storage is a no more than a smart pointer --jcw
2001-02-20
(216.232.136.19)
Note: you are looking at
the snapshot of an old wiki
- much of this information
is likely to be very outdated
