一直想解决这个问题,最近闲了点儿,就顺便完成了这个小模块。
最简单的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; }
下面我们就生成了一个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 }
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; }
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 }
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; }
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; }