在 C++ 和 Lua 协作时,双方的互调用是一个绕不开的话题。通常情况下,我们直接使用 Lua/C API 就可以完成普通的参数传递过程。但在代码中直接操作 lua stack,容易写出繁冗和重复的代码。这时我们往往会借助 tolua++ 之类的库,把参数传递的工作自动化,降低负担。
进一步讲,由于 Lua 的参数传递在个数和类型上非常灵活(任何一个函数可以传递任意个数和类型的参数),有时我们会希望在与 C++ 的互操作时保留这种灵活性,比如 C++ 向 Lua 发一个消息时,如果可以是一个消息 ID 带上任意数量和类型的参数,就会很方便(反过来也一样)。由于 C++ 能通过可变参的模板函数实现类型安全的参数传递,与 Lua 的动态参数列表相结合后,我们就能在一个接口上实现更大的跨语言自由度。
有不少第三方库能够简化 C++ 和 Lua 之间的互调用,这次我们使用 LuaBridge 来完成工作。开始前我们先介绍一下普通的互调用怎么做。
// C side
luabridge::LuaReffoo=luabridge::getGlobal(L,"foo");autoretString=foo("bar",12,0.25f);// 这里先忽略错误处理
接着是 Lua 调 C++:
1
2
3
4
5
6
7
8
9
10
// C side
intCallMe(conststd::string&arg1,conststd::string&arg2){returnstd::stoi(arg1)+std::stoi(arg2);// 同样先不管错误处理
}luabridge::getGlobalNamespace(L).beginNamespace("native").addFunction("call_me",CallMe).endNamespace();
1
2
-- lua sidesum=native.call_me("15","20")-- sum = 35
// C side
intPost(intmsgID,luabridge::LuaRefargs){// 这里的 switch 可以用 template <int N> 来避免分支处理,并消除每一个 case 内重复的代码。具体实现暂略,这里为了清晰直接手写
switch(msgID){caseMsgA:{autot=tuple_cast<std::string,std::string>(args);returnProcessA(std::get<0>(t),std::get<1>(t));}caseMsgB:{autot=tuple_cast<int,float,float>(args);returnProcessB(std::get<0>(t),std::get<1>(t),std::get<2>(t));}}returnFAILED_BAD_ID;}
这里使用 tuple_cast 的好处是把所有的类型转换重复代码收拢到一处,对自定义类型的扩展也很容易。 tuple_cast() 函数本质上是把一个 LuaRef 根据期望类型(由模板参数指定)展开成一个 std::tuple,对于任何一组给定的类型,递归地在编译期完成展开。具体的技术在之前的 blog 中有提到,这里不再赘述。