LuaTinker源码分析,附送一枚小bug

    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中压入的函数指针。
    void2type的实现如下:
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);
     }
};
     invoke实际上利用辅助模板if_ 、is_ptr、is_ref 来做一些区分工作,最终调用的是void2ptr这些真正的模板中的invoke方法。if_、is_ptr、is_ref 实现都比较简单,不赘述。
    其中void2ptr的invoke方法就是将ptr转换成 T类型的指针。T 在这里实际上 就是 void(*)()。看到这里就明白了之前functor带返回值的push 就是把函数执行的结果加入lua的栈中。
    模板推导来推导去,目的是什么呢,实际上就是将普通函数转换成符合标准的C_FUNCTION。

   这种写法同其他的一些粘合层代码典型写法来说(典型的三段式,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);
	}
    class_add 向全局空间中压入一个table ,就是类,通过类名去索引该table,并在table中设置name属性和三个方法,分别是__index, __newindex ,__gc 。
    meta_get 是一个C_FUNCTION ,它是如何实现的呢?
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();这句的。

转载于:https://my.oschina.net/jabbawockeez/blog/292076

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值