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


Last modified
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