Luna

        LUA并不支持直接访问C++的类,但是通过使用LUA的C API和C++ templates,可以在LUA注册C++类,并访问其成员,这种方法叫做Luna。

       LunaWrapper是一个简单的Luna实现,它的具体做法可概括为如下:

1.初始化LunaWrapper时调用其注册函数Register,在Register中通过LUA C API把一个C函数定义为一个全局LUA函数,函数名是LUA中要访问的C++ class类名,以便能在lua中这样创建C++对象:foo = Foo()。这个C函数其实主要是new一个Foo对象出来,关于这个C函数具体实现见下文。在Register中还创建了一个关联C++类名的元表,并且把这个元表的__gc方法设置为一个C函数gc_obj,当LUA中没有谁在引用foo,垃圾回收foo时会调用gc_obj,后面可以看见 gc_obj其实是delete了这个Foo对象。代码见下:

 

  static void Register(lua_State *L) {
      lua_pushcfunction(L, &Luna<T>::constructor);
      lua_setglobal(L, T::className);

      luaL_newmetatable(L, T::className);
      lua_pushstring(L, "__gc");
      lua_pushcfunction(L, &Luna<T>::gc_obj);
      lua_settable(L, -3);
    }


C函数在C++中当然可以使用静态成员函数代替了,至于模板的使用,则方便了导出各种不同的C++类类型。总的来说Register函数是在lua加载LunaWrapper时初始化时调用的函数。

 


2.构造函数。构造函数当然是要创建出一个这样的C++对象,也当然还要做些事情能让LUA访问到它的成员。这里用到了LUA的userdata和metatable,userdata保存C++对象的指针,userdata自己本身则被保存在一个新建的表中,这个表后面可以看见它是构造函数的返回值,LUA中都是直接对它进行操作,也就是说我们需要在内部把LUA对它的访问转掉到C++对象的成员上来,让在LUA中看来是直接在访问C++对象。最后,代码遍历C++类的到处成员函数表,把每个函数名做为key,转调函数Luna<T>::thunk做为值添加进返回表中中。

 

static int constructor(lua_State *L) {
      T* obj = new T(L);

      lua_newtable(L);
      lua_pushnumber(L, 0);
      T** a = (T**)lua_newuserdata(L, sizeof(T*));
      *a = obj;
      luaL_getmetatable(L, T::className);
      lua_setmetatable(L, -2);
      lua_settable(L, -3); // table[0] = obj;

      for (int i = 0; T::Register[i].name; i++) {
        lua_pushstring(L, T::Register[i].name);
        lua_pushnumber(L, i);
        lua_pushcclosure(L, &Luna<T>::thunk, 1);
        lua_settable(L, -3);
      }
      return 1;
    }

 


3.转调函数。它所做的事很简单,转掉C++类的相应成元函数。这里用到了LUA的闭包C API, 把相应的成员函数在数组中的索引值与转掉函数关联起来,目的是能调用到正确的成员函数。当然对象指针也能通过table[0]轻易获得。

 

  static int thunk(lua_State *L) {
      int i = (int)lua_tonumber(L, lua_upvalueindex(1));
      lua_pushnumber(L, 0);
      lua_gettable(L, 1);

      T** obj = static_cast<T**>(luaL_checkudata(L, -1, T::className));
      lua_remove(L, -1);
      return ((*obj)->*(T::Register[i].mfunc))(L);
    }

 

4.析构函数。Luna使用了lua的userdata保存了对象的指针,并且在没有谁引用userdata时,delete它保存的指针。

 

static int gc_obj(lua_State *L) {
      T** obj = static_cast<T**>(luaL_checkudata(L, -1, T::className)); //检查在栈中指定位置的对象是否为带有给定名字的metatable的usertatum
      delete (*obj);
      return 0;
    }

 

因为在构造时这个userdata被放在table[0]位置,而在lua中table对应C++对象,当lua中没有在引用这个table时,table会被gc,其key和value也会gc,而userdata gc时会释放C++对象,这样就做到了生命周期的一致。

LunaWrapper完整代码和测试例子在:https://github.com/persistentsnail/luna-test

参考资料:LunaWrapper


posted @ 2012-09-03 23:37  persistentsnail  阅读(383)  评论(0编辑  收藏  举报