虽然很多动态语言(例如 PHP)的性能在近些年有了大幅度的提升,也得到了更广泛的应用,但是在一些对性能要求比较严苛的场合,C/C++ 还是有着难以替代的优势。可 C/C++ 最大的缺点就是它的不够灵活,很小一点修改都必须得重新编译,部署,重启上线。为了增强 C/C++ 的灵活性,很多项目都选择嵌入 Lua 解析器来处理程序逻辑中的动态部分,我们也不例外。
目前我们对 Lua 的使用还是比较保守,主要是封装了一些基于特定条件的排序或者过滤规则。它的特点就是传入参数较多,但返回值特别少,基本上就是一个数字或者布尔值。最开始是使用的原始方法,手工去拼 Lua Table 作为传入参数,每加一个参数,就要手写几行添加元素的代码。最近我看到 brpc 里的 pb2json ,忽然想到完全可以用 Protobuf 的反射机制,自动拼 Lua Table。下面是基本类型的转换方法,当然,也可以用类似的方法对 Protobuf 的 map, message 等高级数据结构进行进一步封装。
void ProtoMessageToLuaTable(const google::protobuf::Message &message, lua_State *L) { lua_newtable(L); const Descriptor* descriptor = message.GetDescriptor(); const Reflection* reflection = message.GetReflection(); int field_count = descriptor->field_count(); for (int i = 0; i < field_count; ++i) { const FieldDescriptor* field = descriptor->field(i); switch (field->type()) { case FieldDescriptor::TYPE_BOOL: lua_pushboolean(L, reflection->GetBool(message, field)); break; case FieldDescriptor::TYPE_UINT32: lua_pushinteger(L, reflection->GetUInt32(message, field)); break; case FieldDescriptor::TYPE_UINT64: lua_pushinteger(L, reflection->GetUInt64(message, field)); break; case FieldDescriptor::TYPE_INT32: case FieldDescriptor::TYPE_SINT32: lua_pushinteger(L, reflection->GetInt32(message, field)); break; case FieldDescriptor::TYPE_INT64: case FieldDescriptor::TYPE_SINT64: lua_pushinteger(L, reflection->GetInt64(message, field)); break; case FieldDescriptor::TYPE_FLOAT: lua_pushnumber(L, static_cast<double>(reflection->GetFloat(message, field))); break; case FieldDescriptor::TYPE_DOUBLE: lua_pushnumber(L, reflection->GetDouble(message, field)); break; case FieldDescriptor::TYPE_STRING: lua_pushstring(L, reflection->GetString(message, field).c_str()); break; default: lua_pushnil(L); break; } lua_setfield(L, -2, field->name().c_str()); } }
其实调研了一下,发现还有一些其它的方法,比如 luabind, sol2 等一堆库。但这些工具更适合 C++ Lua 交互比较复杂的场合,而且也引入了额外的依赖和额外的要求(比如 C++11)。对于像我们这样的简单场景,在不引入更多依赖的情况下使用 Protobuf 反射机制,不失为一个好的选择。