在C++中调用lua函数的一般方式如下:
void callfunc1(lua_State* L, int arg1, const string& arg2)//调用脚本中的func1函数,参数为一个number,一个string { lua_getglobal(L, "func1"); lua_pushnumber(L, arg1); lua_pushstring(L, arg2.c_str()); lua_pcall(L, 2, LUA_MULTRET, 0); } void callfunc2(lua_State* L, int arg1, int arg2)//调用脚本中的func2函数,参数为两个number { lua_getglobal(L, "func2"); lua_pushnumber(L, arg1); lua_pushnumber(L, arg2); lua_pcall(L, 2, LUA_MULTRET, 0); }
如果有很多个脚本函数需要调用,按上面的方式就要写很多个与之对应的C++函数,当然也可以利用C++的重载,把函数名作为参数,每种参数组合实现一个重载函数,则上面的代码可以改为:
void common_call(lua_State* L, const char* funname, int arg1, const string& arg2) { lua_getglobal(L, funname); lua_pushnumber(L, arg1); lua_pushstring(L, arg2.c_str()); lua_pcall(L, 2, LUA_MULTRET, 0); } void common_call(lua_State* L, const char* funname, int arg1, int arg2) { //... }
如果有两个不同的lua函数,它们的参数是一样的,则可以共用同一个common_call,但是不同的参数组合就必须有一个与之对应的common_call,实现起来还是很麻烦,重复代码仍然很多。《Programming in Lua》上提供了一种通用的办法,用变长参数实现的,见http://www.lua.org/pil/25.3.html 但是仍然觉得不是太好,那个描述字符串很难看!一番思考后折腾出了下面这个方法:
对于不同类型的参数,唯一不同的就是压栈时的API不一样,数字使用lua_pushnumber,bool型是用lua_pushbool,字符串是用lua_pushstring等,所以抽象出一个参数基类来,提供一个虚拟的pushvalue接口,然后各种实际类型实现自己的pushvalue就可以了,代码如下:
#define my_lua_dofile(L, filename) ( luaL_loadfile((L), (filename)) || lua_pcall((L), 0, LUA_MULTRET, 0) ) class TArg { public: TArg(){} virtual void pushvalue(lua_State* L)const = 0; }; class TArgInt:public TArg { int _intv; public: explicit TArgInt(int v):_intv(v){} virtual void pushvalue(lua_State* L) const {lua_pushinteger(L, _intv);} }; class TArgStr:public TArg { string _strv; public: explicit TArgStr(const string& v):_strv(v){} virtual void pushvalue(lua_State* L) const {lua_pushstring(L, _strv.c_str());} }; class TArgBool:public TArg { bool _boolv; public: explicit TArgBool(bool v):_boolv(v){} virtual void pushvalue(lua_State* L) const {lua_pushboolean(L, _boolv);} }; int call(lua_State* L, const char* fname, const vector
看起来问题已经解决了,但是仍然不够彻底,用户需要自己去构造一个TArg*的vector,对于每一种参数类型,用户需要知道它对于的基类是什么,下面把参数做进一步的封装:
class TArgPool { std::vector
void TArgPool::AddArg(int Value) { TArgInt* pObj = new TArgInt(Value); ArgList.push_back(pObj); } void TArgPool::AddArg(const std::string& Str) { TArgStr* pObj = new TArgStr(Str); ArgList.push_back(pObj); } void TArgPool::AddArg(bool Value) { TArgBool* pObj = new TArgBool(Value); ArgList.push_back(pObj); } int TArgPool::Push(lua_State* L)const { for (size_t i = 0; i < ArgList.size(); i++) { ArgList[i]->pushvalue(L); } return ArgList.size(); } TArgPool::~TArgPool() { for (size_t i = 0; i < ArgList.size(); i++) { delete ArgList[i]; } ArgList.clear(); } int CallLua(lua_State* L, const char* fname, const TArgPool& ArgPoolObj) { lua_getglobal(L, fname); if (lua_isfunction(L, -1) ) { if ( 0 == lua_pcall(L, ArgPoolObj.Push(L), LUA_MULTRET, 0) ) return 0; fprintf(stderr, "%s:%d call function failed:%s/n", __FILE__, __LINE__, luaL_checkstring(L, -1)); } return -1; }
然后用户需要调用lua函数的时候只需要构造一个TArgPool对象就行了:
... TArgPool ArgPoolObj; ArgPoolObj.AddArg(socket_fd); ArgPoolObj.AddArg(std::string(dataptr, length)); CallLua(LState, read_ref, ArgPoolObj); ...
这样基本上满足需求了~