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();这句的。