有时候我们需要通过C++向Lua传递包含多种类型数据的参数,但这些参数又没办法提前预知,也就是说,应用可能会有不同类型的参数,这些参数需要统一传入Lua进行处理。很显然,如果每次都要去判断每一个参数的类型,并调用不同的传递函数lua_push***,在代码上就显得拖沓冗余,并且效率低下。那么有没有办法定义一个统一的接口,能够接收常用的数据类型,并统一传给Lua呢?
可以利用类的多态性实现。
假如我们传递的数据类型包括int、double、bool、string,那么,让我们来定义一个基类和若干子类。
基类:
/* A base class for arguments from C/C++ to lua */
class TArg
{
public:
TArg(){}
virtual void push_value(lua_State* L) const = 0;
virtual void set_value(int v){} //set_value()函数用于更新数据时使用
virtual void set_value(double v){}
virtual void set_value(bool v){}
virtual void set_value(std::string v){}
};
int子类:
/* The derived class for the int type which will be pushed from C++ to Lua stack */
class TArgInt : public TArg
{
public:
explicit TArgInt(int v) : _numv(v){}
virtual void push_value(lua_State* L) const
{
lua_pushinteger(L, _numv);
}
virtual void set_value(int v)
{
_numv = v;
}
private:
int _numv;
};
double子类:
/* The derived class for the double type which will be pushed from C++ to Lua stack */
class TArgDouble : public TArg
{
public:
explicit TArgDouble(double v) : _numv(v){}
virtual void push_value(lua_State* L) const
{
lua_pushnumber(L, _numv);
}
virtual void set_value(double v)
{
_numv = v;
}
private:
double _numv;
};
string子类:
/* The derived class for the string type which will be pushed from C++ to Lua stack */
class TArgString : public TArg
{
public:
explicit TArgString(std::string v) : _strv(v){}
virtual void push_value(lua_State* L) const
{
lua_pushstring(L, _strv.c_str());
}
virtual void set_value(std::string v)
{
_strv = v;
}
private:
std::string _strv;
};
bool子类:
/* The derived class for the bool type which will be pushed from C++ to Lua stack */
class TArgBool : public TArg
{
public:
explicit TArgBool(bool v) : _boolv(v){}
virtual void push_value(lua_State* L) const
{
lua_pushboolean(L, _boolv);
}
virtual void set_value(bool v)
{
_boolv = v;
}
private:
bool _boolv;
};
好了,定义好了我们可能需要的类型对应的各个class,那么怎么把它们统一起来呢?通过参数池的方式,把所有这些参数统一放进一个列表std::vector<TArg*> arg_list。所以,接下来,我们来定义一个参数池类:
/* Define an argument pool for multi-type arguments storage */
class TArgsPool
{
public:
TArgsPool()
{
arg_list.clear();
b_new_list = true;
}
/* Destructor for TArgsPool, Free all the allocated memory and clear the list */
~TArgsPool()
{
for(int i = 0; i < arg_list.size(); i++)
{
delete arg_list[i];
}
arg_list.clear();
}
/* The first time, when arg_list is empty, add_arg_to_pool would be called */
/* 将int型数据加入参数池 */
void add_arg_to_pool(int iv)
{
TArg *pArg = new TArgInt(iv);
arg_list.push_back(pArg);
}
/* 将double型数据加入参数池 */
void add_arg_to_pool(double dv)
{
TArg *pArg = new TArgDouble(dv);
arg_list.push_back(pArg);
}
/* 将string型数据加入参数池 */
void add_arg_to_pool(std::string sv)
{
TArg *pArg = new TArgString(sv);
arg_list.push_back(pArg);
}
/* 将bool型数据加入参数池 */
void add_arg_to_pool(bool bv)
{
TArg *pArg = new TArgBool(bv);
arg_list.push_back(pArg);
}
/* 对于一种参数类型组合,如果存在多次传递需求,例如http的多次请求,则每次只需要更新参数即可,无需反复删除和新增 */
/* When the arg_list is completely generated, update_arg_to_pool should be called */
void update_arg_to_pool(int iv, int idx)
{
if((idx < 0) || (idx >= arg_list.size()))
{
std::cout << "Error: Invalid argument list index!" << std::endl;
exit(1);
}
std::cout << "Update int value to " << iv << ", at " << idx << std::endl;
arg_list[idx]->set_value(iv);
}
void update_arg_to_pool(double dv, int idx)
{
if((idx < 0) || (idx >= arg_list.size()))
{
std::cout << "Error: Invalid argument list index!" << std::endl;
exit(1);
}
std::cout << "Update double value to " << dv << ", at " << idx << std::endl;
arg_list[idx]->set_value(dv);
}
void update_arg_to_pool(std::string sv, int idx)
{
if((idx < 0) || (idx >= arg_list.size()))
{
std::cout << "Error: Invalid argument list index!" << std::endl;
exit(1);
}
std::cout << "Update string value to " << sv << ", at " << idx << std::endl;
arg_list[idx]->set_value(sv);
}
void update_arg_to_pool(bool bv, int idx)
{
if((idx < 0) || (idx >= arg_list.size()))
{
std::cout << "Error: Invalid argument list index!" << std::endl;
exit(1);
}
std::cout << "Update bool value to " << bv << ", at " << idx << std::endl;
arg_list[idx]->set_value(bv);
}
/* Push all the arguments in arg_list to lua stack */
int push_args_to_stack(lua_State *L)
{
int arg_num = (int)arg_list.size();
for(int i = 0; i < arg_num; i++)
{
arg_list[i]->push_value(L);
}
return arg_num;
}
/* When the first time arguments were added, reset the new list flag to false */
void reset_new_list_flag()
{
b_new_list = false;
}
/* Check if argument list is new */
bool check_if_new_list()
{
return b_new_list;
}
private:
std::vector<TArg*> arg_list;
bool b_new_list;
};
该类中,当第一次收集参数时,通过添加的方式将参数添加到数据池中,add_arg_to_pool()方法;第二次同样的参数到来时,则使用更新的方式,update_arg_to_pool()方法,避免不断地释放和分配内存。然后通过方法push_args_to_stack()将所有参数推入lua栈。
测试一下:
/* 初始化函数 */
lua_State* script_initialize(const char* script_name)
{
lua_State *pLua = luaL_newstate(); //创建一个Lua虚拟机
if(!pLua)
{
std::cout << "Failed to open Lua!" << std::endl;
return NULL;
}
luaL_openlibs(pLua); //将标准类库加载到Lua虚拟机
luaL_dofile(pLua, script_name); //加载并运行lua文件
return pLua;
}
int main()
{
int test_int = 10;
std::string test_str = "This is a test string.";
std::string test_str1 = "Another test string.";
double test_dbl = 3.1415926;
bool test_bool = true;
lua_State *pL = script_initialize("multi_args.lua");
if(!pL)
{
std::cout << "Script initialized failed!" << std::endl;
return -1;
}
lua_getglobal(pL, "print_args"); //获取lua函数print_args
if(!lua_isfunction(pL, -1))
{
std::cout << "Get lua function failed!" << std::endl;
exit(1);
}
/* 定义一个数据池对象 */
TArgsPool data_pool;
/* 测试添加数据功能 */
data_pool.add_arg_to_pool(test_int);
data_pool.add_arg_to_pool(test_str);
data_pool.add_arg_to_pool(test_str1);
data_pool.add_arg_to_pool(test_dbl);
data_pool.add_arg_to_pool(test_bool);
/* 将以上数据池的数据都推入lua栈 */
int data_num = data_pool.push_args_to_stack(pL);
/* 调用lua函数打印数据 */
lua_pcall(pL, data_num, 0, 0);
/* 清空lua栈 */
while(lua_gettop(pL))
{
lua_pop(pL, 1);
}
lua_getglobal(pL, "print_args");
if(!lua_isfunction(pL, -1))
{
std::cout << "Get lua function failed!" << std::endl;
exit(1);
}
/* 测试更新数据功能 */
data_pool.update_arg_to_pool((int)100, 0);
data_pool.update_arg_to_pool((std::string)"Hello", 1);
data_pool.update_arg_to_pool((std::string)"Lua", 2);
data_pool.update_arg_to_pool((double)9.12345, 3);
data_pool.update_arg_to_pool((bool)false, 4);
/* 将更新后的数据推入lua栈并调用lua打印函数 */
data_num = data_pool.push_args_to_stack(pL);
lua_pcall(pL, data_num, 0, 0);
lua_close(pL);
return 0;
}
Lua脚本:
function print_args(...)
print("-----This is lua function-----")
local args = {...}
for k, v in pairs(args) do
print(v)
end
return 0;
end
运行结果: