LuaTinker是一个功能比较强大的粘合层。
包括以下多种用例:
1.def 向lua核心中压入函数。
2.call 调用lua中的函数。
3.set 向lua中设置一个变量。
4.get 从lua中获取一个变量的值。
5.class_add向lua中增加一个类。
6.class_def 定义一个成员函数。
7.class_inh 定义类之间继承关系。
8.class_con 定义构造函数。
9.class_mem 定义类成员。
10.table定义一个table类,模拟lua中自带的table的行为。
从以上方法中,挑一些有特色的分析吧。
1.def方法:
源码如下:
// global function template<typename F> void def(lua_State* L, const char* name, F func) { lua_pushstring(L, name); lua_pushlightuserdata(L, (void*)func); push_functor(L, func); lua_settable(L, LUA_GLOBALSINDEX); }
函数先压入name,再压入函数指针(地址),之后在push_functor,最后在LUA_GLOBALSINDEX 注册。
那么push_functor是什么呢?push_functor是一系列的辅助模板,用来自动推导函数的参数和返回值。
模板原型如下:
template<typename RVal> void push_functor(lua_State *L, RVal (*func)()) { lua_pushcclosure(L, functor<RVal>::invoke, 1); }
push_functor 实际上是调用的lua_pushcclosure来完成向lua中压入函数的。此处upvalue的参数为1 说明之前压入的函数指针变成了这个cfunction的upvalue,functor也是一系列模板实现的,functor分为两种,一种带有返回值,一种不带有返回值。带返回值的实现如下:
template<typename RVal, typename T1> struct functor<RVal,T1> { static int invoke(lua_State *L) { push(L,upvalue_<RVal(*)(T1)>(L)(read<T1>(L,1))); return 1; } };
不带返回值的实现如下:
template<> struct functor<void> { static int invoke(lua_State *L) { upvalue_<void(*)()>(L)(); return 0; } };
可以看到invoke方法中实际上调用upvalue_<函数原型>(L)(); upvalue_是什么呢?其实也是一个辅助模板函数:
// get value from cclosure template<typename T> T upvalue_(lua_State *L) { return user2type<T>::invoke(L, lua_upvalueindex(1)); }又调用 user2type中的invoke方法,由于之前已经关联的upvalue,所以此处的第二个参数为lightuserdata 也即我们之前push进来的函数指针。user2type通过名字也知道是将userdata转换成具体的type的,它的实现如下:
template<typename T> struct user2type { static T invoke(lua_State *L, int index) { return void2type<T>::invoke(lua_touserdata(L, index)); } };lua_touserdata将索引index处的userdata或者lightuserdata 转化成了地址或者指针,也即def中压入的函数指针。
template<typename T> struct void2type { static T invoke(void* ptr) { return if_<is_ptr<T>::value ,void2ptr<typename base_type<T>::type> ,typename if_<is_ref<T>::value ,void2ref<typename base_type<T>::type> ,void2val<typename base_type<T>::type> >::type >::type::invoke(ptr); } };
这种写法同其他的一些粘合层代码典型写法来说(典型的三段式,push,push,rawset),确实比较优雅。
2.call,set,get方法都实现的比较质朴,有兴趣的可以找源码看看。
3.class_add方法:
// class init template<typename T> void class_add(lua_State* L, const char* name) { class_name<T>::name(name); lua_pushstring(L, name); lua_newtable(L); lua_pushstring(L, "__name"); lua_pushstring(L, name); lua_rawset(L, -3); lua_pushstring(L, "__index"); lua_pushcclosure(L, meta_get, 0); lua_rawset(L, -3); lua_pushstring(L, "__newindex"); lua_pushcclosure(L, meta_set, 0); lua_rawset(L, -3); lua_pushstring(L, "__gc"); lua_pushcclosure(L, destroyer<T>, 0); lua_rawset(L, -3); lua_settable(L, LUA_GLOBALSINDEX); }
int lua_tinker::meta_get(lua_State *L) { lua_getmetatable(L,1); lua_pushvalue(L,2); lua_rawget(L,-2); if(lua_isuserdata(L,-1)) { user2type<var_base*>::invoke(L,-1)->get(L); lua_remove(L, -2); } else if(lua_isnil(L,-1)) { lua_remove(L,-1); invoke_parent(L); if(lua_isnil(L,-1)) { lua_pushfstring(L, "can't find '%s' class variable. (forgot registering class variable ?)", lua_tostring(L, 2)); lua_error(L); } } lua_remove(L,-2); return 1; }
当lua中使用.或者[]方法时,实际上是触发了table的__index方法。代码前3行分别向L中压入了key的元表,key。通过rawget从table中获取key值对应的元素的地址,压入L中,之后判断站定的值是指针还是空,假设是指针的话,通过user2type的方法get来获取他的值,如果为空的话则递归查找它的父类中是否有key这个属性。meta_set方法类似。
4.class_inh方法:
这个方法用来定义类之间的继承关系,实现如下:
// Tinker Class Inheritence template<typename T, typename P> void class_inh(lua_State* L) { push_meta(L, class_name<T>::name()); if(lua_istable(L, -1)) { lua_pushstring(L, "__parent"); push_meta(L, class_name<P>::name()); lua_rawset(L, -3); } lua_pop(L, 1); }
意思是在类T对应的表中设置一个名为__parent的字段,保存的是类p。再来看看 meta_get 中invoke_parent方法是如何实现的:
static void invoke_parent(lua_State *L) { lua_pushstring(L, "__parent"); lua_rawget(L, -2); if(lua_istable(L,-1)) { lua_pushvalue(L,2); lua_rawget(L, -2); if(!lua_isnil(L,-1)) { lua_remove(L,-2); } else { lua_remove(L, -1); invoke_parent(L); lua_remove(L,-2); } } }
实际上就是找到当前元表中找到名为__parent 的字段,如果之前用class_inh定义了类之间关系的话,__parent必然是一个表,也就是父类,接着在父类中查找key,递归查找父类的父类是否含有key。
5.附送小Bug一个:
table类,是用来模拟Lua自带的table的行为的,table类实现非常简单,它只有一个成员,就是名为table_object类的指针,table_object中包含有引用计数,用来释放table_object对象。Bug发生在table类的构造时,请看构造函数的定义:
lua_tinker::table::table(lua_State* L) { lua_newtable(L); m_obj = new table_obj(L, lua_gettop(L)); m_obj->inc_ref(); } lua_tinker::table::table(lua_State* L, const char* name) { lua_pushstring(L, name); lua_gettable(L, LUA_GLOBALSINDEX); if(lua_istable(L, -1) == 0) { lua_pop(L, 1); lua_newtable(L); lua_pushstring(L, name); lua_pushvalue(L, -2); lua_settable(L, LUA_GLOBALSINDEX); } m_obj = new table_obj(L, lua_gettop(L)); } lua_tinker::table::table(lua_State* L, int index) { if(index < 0) { index = lua_gettop(L) + index + 1; } m_obj = new table_obj(L, index); m_obj->inc_ref(); } lua_tinker::table::table(const table& input) { m_obj = input.m_obj; m_obj->inc_ref(); }
第二个使用名字的构造函数,在new出m_obj之后,忘记递增m_obj的计数了,应该加上m_obj->inc_ref();这句的。