换一种思路实现c调用lua函数

一直想解决这个问题,最近闲了点儿,就顺便完成了这个小模块。

最简单的c回调lua函数大家可能都比较熟悉了,对于调用lua中的全局函数,一个lua_getglobal和lua_call就行了,但是这需要宿主程序内写死lua中的函数名,耦合性太强,我正是因为以前实现的事件通知方式有问题才想换一种方法实现的。

后来,我又用了另外一种方式,即函数名注册方式,lua中写了一个函数,并将这个函数名注册给宿主,宿主在特定条件下调用该函数名的函数。这种方式解决了耦合性问题,但是使用不大方便,传入参数过多,并且字符串的匹配调用都有一点儿性能损耗。

后来想起cocos2dx内似乎使用了函数引用来调用lua中的函数,的确是一种不错的方法。宿主程序只要在特定事件的时候调用所有注册到该事件的函数,即可完成lua函数的调用,耦合性非常小,于是自己实现了一个简单的使用数字引用方式调用lua函数的方法。


核心思想即为在lua提供的注册表中添加一个table,该table的key就是所谓的lua函数所以,value就是lua函数的值,每次调用lua函数的时候,根据ref直接可以找到function,那么直接可以调用该lua函数了。


下面我们一步一步来。首先需要在全局的注册表中注册一个table。cocos2dx中使用了string来做key,这儿我根据云风大神的文章,使用了lightuserdata当key,这样key的索引时间会少一点儿,记住不能用number当key,这是被lua保留的。


//////////////////////////////////////////////////////////////////////////
//	static light userdata key
static int s_nLuaFunctionRefKey = 0;
void* GetLuaFunctionRefKey()
{
	return (void*)&s_nLuaFunctionRefKey;
}

//	static lua function reference id
static int s_nLuaFunctionRefId = 0;
int GetLuaFunctionRefId()
{
	return ++s_nLuaFunctionRefId;
}

在这里我们使用了一个静态变量的地址当做key,并且用了一个int当做ref的索引生成器。


下面我们就生成了一个table来记录ref和func。

void luaext_open(lua_State* L)
{
#ifdef _DEBUG
	int nStackTop = lua_gettop(L);
#endif
	//	push key
	lua_pushlightuserdata(L, GetLuaFunctionRefKey());	//	stack: key
	//	push value
	lua_newtable(L);	//	stack: key table
	//	set global table key
	lua_rawset(L, LUA_REGISTRYINDEX);	//	stack:
#ifdef _DEBUG
	assert(nStackTop == lua_gettop(L));
#endif
}

然后我们要实现的,就是注册lua函数到这个表中。也很简单,设置该table的值即可。

int luaext_registerLuaFunction(lua_State* L, int _nFuncIndex)
{
	if(_nFuncIndex < 0)
	{
		return 0;
	}
	if(!lua_isfunction(L, _nFuncIndex))
	{
		return 0;
	}

	//	push key
	lua_pushlightuserdata(L, GetLuaFunctionRefKey());	//	stack: func ... key
	//	get ref table
	lua_rawget(L, LUA_REGISTRYINDEX);		//	stack: func ... reftable

	if (!lua_istable(L, -1))
	{
		return 0;
	}

	//	push new key
	int nRefId = GetLuaFunctionRefId();
	lua_pushnumber(L, nRefId);			//	stack: func ... reftable key
	//	copy function value from stack
	lua_pushvalue(L, _nFuncIndex);		//	stack: func ... reftable key value
	//	set key
	lua_rawset(L, -3);	//	stack: func ... reftable
	//	pop table
	lua_pop(L, 1);

	return nRefId;
}

既然做到了注册,那么也要做到反注册,也很简单,设置key=nil即可。

void luaext_unregisterLuaFunctionByRefId(lua_State* L, int _nRefId)
{
#ifdef _DEBUG
	int nStackTop = lua_gettop(L);
#endif
	//	push key
	lua_pushlightuserdata(L, GetLuaFunctionRefKey());	//	stack: func ... key
	//	get ref table
	lua_rawget(L, LUA_REGISTRYINDEX);		//	stack: func ... reftable

	if (!lua_istable(L, -1))
	{
		return;
	}

	//	push the key
	lua_pushnumber(L, _nRefId);	//	stack: reftable key
	//	push nil
	lua_pushnil(L);			//	stack: reftable key nil
	//	set nil
	lua_rawset(L, -3);
	//	pop reftable
	lua_pop(L, 1);

#ifdef _DEBUG
	assert(lua_gettop(L) == nStackTop);
#endif
}

这个反注册是根据ref来反注册的,当然可能直接需要根据func来反注册,实现也很简单,遍历table,设置nil即可。

int luaext_unregisterLuaFunction(lua_State* L, int _nFuncIndex)
{
	if(_nFuncIndex < 0)
	{
		return 0;
	}
	if(!lua_isfunction(L, _nFuncIndex))
	{
		return 0;
	}

	int nCount = 0;

	//	push key
	lua_pushlightuserdata(L, GetLuaFunctionRefKey());	//	stack: key
	//	get ref table
	lua_rawget(L, LUA_REGISTRYINDEX);		//	stack: reftable

	if (!lua_istable(L, -1))
	{
		return 0;
	}

	//	push reftable index
	int nStackTop = lua_gettop(L);

	//	push init key
	lua_pushnil(L);		//	stack: reftable nil_key

	while(lua_next(L, nStackTop))
	{
		//	stack: reftable key value
		if(lua_isfunction(L, -1))
		{
			//	valid function
			if(lua_equal(L, _nFuncIndex, -1))
			{
#ifdef _DEBUG
				int nTop = lua_gettop(L);
#endif
				//	set refFunc[key] = nil
				//	push key
				lua_pushvalue(L, -2);
				//	push nil
				lua_pushnil(L);
				//	stack: reftable key value key nil
				lua_rawset(L, -5);	//	stack: reftable key value

#ifdef _DEBUG
				assert(lua_gettop(L) == nTop);
#endif
				++nCount;
			}
		}

		//	pop the value, and use the key to get the next key
		lua_pop(L, 1);	//	stack: reftable key 
	}

#ifdef _DEBUG
	assert(lua_gettop(L) == nStackTop);
#endif

	return nCount;
}

做到了这些工作,我们使用的时候,就需要一个lua_getglobal类似的函数了。我们来实现一个getLuaFunctionByRefId来push要调用的函数到lua栈中。

int luaext_getLuaFunctionByRefId(lua_State* L, int _nRefId)
{
#ifdef _DEBUG
	int nStackTop = lua_gettop(L);
#endif

	//	push key
	lua_pushlightuserdata(L, GetLuaFunctionRefKey());	//	stack: key
	//	get ref table
	lua_rawget(L, LUA_REGISTRYINDEX);		//	stack: reftable

	if (!lua_istable(L, -1))
	{
		return 0;
	}

	//	push key
	lua_pushnumber(L, _nRefId);	//	stack: reftable key
	//	get value
	lua_rawget(L, -2);		//	stack: reftable (value or nil)

	int nRetId = 0;

	if(lua_isfunction(L, -1))
	{
		nRetId = _nRefId;

		//	stack: reftable value
		lua_remove(L, -2);	//	stack: value(func)

#ifdef _DEBUG
		assert(lua_gettop(L) == nStackTop + 1);
#endif
	}
	else
	{
		//	pop reftable and nil
		lua_pop(L, 2);
#ifdef _DEBUG
		assert(lua_gettop(L) == nStackTop);
#endif
	}

	return nRetId;
}

上面就是核心功能的简单实现。上层实现的话各有各的思路,通过链表来保存所有注册事件的ref就行了,调用的时候调用全部的ref,这儿我也不细说了。然后就要把以前所用的各种lua_getglobal和注册函数名方式调用的函数都重构一下,又是个坑。

你可能感兴趣的:(换一种思路实现c调用lua函数)