对话系统

现在网易云信 IM SDK 支撑全渠道,IM SDK 每次发版除了要针对新功能进行测验外,回归测验也占了很大比重,跟着 IM SDK 支撑的渠道越来越多,API 接口越来越丰富,每次需求回归测验的规模也在不断添加,如果以人工来完结,不仅效率低下,而且可能会产生漏测、误判等问题。为了进步回归效率、继续监控程序质量,云信在“质量保障体系”建设中投入了很多精力来搭建和完善“主动化测验渠道”,“API 主动化测验”作为主动化测验渠道的重要组件,为“单元测验”、“回归测验”、“每日构建”等功能供给了有力支撑,接入主动化测验渠道十分有含义,其间“API 主动化测验”的主要流程如下图所示,本文就“依据 API 信息转为 API 调用”、“API 调用成果转化为用例履行成果数据”在桌面端(Windows、MacOSX、Linux)完结的关键技术进行评论。

C++ 静态反射在网易云信 SDK 中的实践

为什么要运用反射

下图所展示的内容是由“主动化测验渠道”下发的用例信息(为了便利展示,字段有裁剪)。

C++ 静态反射在网易云信 SDK 中的实践

其间className、methodName描绘了 SDK-API的 NIMAuthService::login 办法,params结点则描绘了login办法的参数,依据上节说到的“依据 API 信息转为 API 调用”、“API 调用成果转化为用例履行成果数据”,其间涉及到转化有两种:

  • API 转化

    依据用例传入的className、methodName来找到对应的 API。

    从上图的数据上来看要找到NIMAuthService::login进行调用。

  • 数据转化

    JsonData2cppStruct,测验渠道 Json 数据转化到 C++ 数据。

    cppStruct2JsonData,C++ 履行成果数据转化为 Json 数据上报到测验渠道。

    从上图的数据上来看要把data节点的数据转化为NIMAuthInfo,做为NIMAuthService::login的调用参数.

    对于这种场景运用“反射”无疑是最适宜与优雅的办法,为什么这么说呢,能够进行如下的比照:

IM SDK-API(仅示例):

C++ 静态反射在网易云信 SDK 中的实践

不运用反射机制,需求依据输入的 Json 数据来查找对应的 service,并找到对应的办法,把 Json 数据转化为对应的 C++ 结构,然后进行 API 调用,基中会涉及到很多的重复逻辑的判断。

C++ 静态反射在网易云信 SDK 中的实践

引进反射机制之后:

C++ 静态反射在网易云信 SDK 中的实践

在进行 API 调用时,能够直接从SDKAPIRefManager中查询已注册的反射信息进行 API 调用。

引进反射的含义 :

对于一致、通用的接口描绘,运用反射来完结从无类型数据结构运行时参数,查找 API 并完结调用。

  • 测验渠道,完结 SDK 渠道无关性、业务一致性测验。
  • 测验接入程序具有“接入快捷”,“兼容多版别 SDK”、“易维护”的特性。

配合代码的主动化生成,在不投入过多精力的情况下,也能够做到程序质量的有用提升。关于“API 反射信息注册”会在下面的章节中进行具体的介绍。

反射的完结原理

对于 Java、Object-c、C# 的程序员来说”反射“是很常用的机制,但 C++ 出于性能及言语特性的考虑,并没有完结反射机制,C++ 尽管没有供给反射的完结,但强壮的 C++ 言语能够很便利的完结反射机制。

为了便利进行转化运用的 Json 库为 nlohmann/json(github.com/nlohmann/js…

能够先看下面示例代码:

C++ 静态反射在网易云信 SDK 中的实践

输出成果:

C++ 静态反射在网易云信 SDK 中的实践

观察示例中的代码能够发现,经过获得类成员变量的地址来完结对具体类实例目标成员进行调用。

auto index_addr = &Test::index;

auto name_addr = &Test::name;

auto fun_print_addr = &Test::printInfo;

test.*index_addr = 2;

test.*name_addr = “test_2”;

(test.*fun_print_addr)();

而这种机制,也是咱们完结 C++ 反射的基石。

反射的完结主要能够分为三个方面:

  • 元数据生成

    元数据是 C++ 目标的描绘信息,保存了 C++ 大局函数、大局目标、目标的成员函数及成员变量的地址、用于反射查找的名称等信息,参阅示例代码,能够把auto index_addr、auto name_addr、auto fun_print_addr理解为元数据。

  • 元数据反射

    元数据反射能够理解为依据外部输入的反射信息包含目标名称、数据的格局化字符串(比方 Json、xml 等)、办法名称、生成对应的 C++ 目标或查找对应的 C++ 办法。

  • API 调用

    依据查找到办法及依据数据的格局化字符串生成的 C++ 目标来完结相应的 API 调用。

按照元数据生成的机遇,反射能够分为两种类型:

  • 动态反射

    在程序运行时才会生成对应类型的元数据信息,优点是需求转化时才生成元数据信息,占用内存小,缺陷是运行速度稍慢。

  • 静态反射

    在程序的编译期生成元数据信息,优点是元数据不需求动态生成,运行速度稍快,缺陷是包体积与内存占用会添加。

    对于主动化测验这个场景,在内部运用,对包巨细、内存占用等没有太大要求,为了进步用例履行效率以及反射代码的主动生成的快捷性,咱们采用了静态反射。

静态反射的完结

1. 结构体的元数据信息保存

如上面的示例代码,struct Test 的每个字段都能够获得其在类中的偏移&类型信息,把偏移&类型信息及变量名称保存下来,生成字段的反射信息,struct 做为反射信息查找的关键字。

C++11 供给了 std::tuple,能够很便利的存储这些信息,形如:

C++ 静态反射在网易云信 SDK 中的实践

经过特化 nlohmann::adl_serializer 结合 struct 的元数据信息来完结 struct 的to_json、from_json。

关于这部分内容已有大神给出了比较具体的介绍《现代 C++ 编译时 结构体字段反射》

(zhuanlan.zhihu.com/p/88144082)

2. API的元数据信息保存

从”测验服务器“上下发的测验用例,SDK-API 参数以 Json 的格局传递,因为掌握了结构体与 Json 的数据的转化,如果把 API 的调用转化为字符串映射,即对 SDK-API 进行类型擦除,在进行 API 调用时依据 SDK-API注册信息,把 jsonArray/stringArray 转化 tuple 结构(例如 std::tuple),然后进行 tupleCall,完结 API 的调用。

映射联系如下:

C++ 静态反射在网易云信 SDK 中的实践

所以有如下的界说:

C++ 静态反射在网易云信 SDK 中的实践

3. API 类型擦除

限于篇幅,只介绍一下静态函数的处理,所谓 API 类型擦除是指把不同类型的 API 接口一致到一样的界说办法,这儿咱们指定的一致界说using api_function_wrapper_t = std::function<function_return_t(const function_param_list_t&)>;

function_return_t:API 返回值的 Json 格局字符串。

function_param_list_t:API 参数的 Json 格局字符串。

JsonParam2Tuple办法就是依据“结构体反射信息”,把测验渠道下发的 Json 参数映射为 tuple 结构,例如 std::tuple。

MakeInvokeApi用于生成类型擦除的通用 API 调用目标

(api_function_wrapper_t)

_InvokeStaticApi能够理解为 SDK-API 的真实调用,它保留了SDK-API的原始描绘,并负责把 JsonParams 转化为 tuple,调用 tupleCall,获得返回值后把 cppParam 转化为 JsonParam。

C++ 静态反射在网易云信 SDK 中的实践

4. API 调用

经过注册信息来调用相应的 API,经过 api_name 来找到已注册的元数据信息,取到 api_wrapper,进行 API 调用,与普通函数调用无异。

C++ 静态反射在网易云信 SDK 中的实践

5. 关于回调的处理

在 SDK 的模拟代码中有 NIMAuthService::loginEx 的界说,基参数列表是一个LoginCallback,它是一个函数目标,无法经过 Json 来进行格局化,此刻 loginEx 转化的 param_list[1](LoginCallback)仅相当于一个占位符,它的类型,能够在注册 API 时进行推导,然后经过JsonToStruct的特化来进行处理,生成能够与 SDK-API 兼容的回调目标,在生成的回调目标履行中,把回调成果通知出去。

特化及示例代码:

C++ 静态反射在网易云信 SDK 中的实践

对应的完结代码:

C++ 静态反射在网易云信 SDK 中的实践

回调的注册能够运用宏来完结:

C++ 静态反射在网易云信 SDK 中的实践

示例代码总览:

C++ 静态反射在网易云信 SDK 中的实践

输出成果:

C++ 静态反射在网易云信 SDK 中的实践

C++ 静态反射在 IM SDK 开发中的使用

反射信息的生成

IM SDK 接供给了很多接口,在生成反射信息时,如果依靠人肉手撸代码的办法会存在以下几个问题:

  • 工作量巨大
  • 简单出错
  • API 改动,需求查找出对应的反射信息,一一进行修改

所以咱们引进了 libclang 来主动生成反射代码,根据 clang 的源到源转译东西能够参阅咱们的一篇共享文章《NeCodeGen:根据 clang 的源到源转译东西》。

主动生成的代码如下所示,节选部分代码片段,其间nim_api::NIM_AuthInfo为云信 IM SDK(elite 版)中关于登录服务的封装,NIMAuthService是反射信息的注册器。

C++ 静态反射在网易云信 SDK 中的实践

使用场景

  • 主动化测验

    前面几个章节在介绍 C++ 反射完结时特定了“API 主动化的场景”,起初引进 C++ 反射也是为了完结主动化测验的接入,现在运用反射机制封装的 C++ 主动化测验接入 SDK 已完结了桌面端的覆盖,并达到了与 Java/object-c 相同的效果,测验团队只需求编写一份测验用例,就能够对现在 SDK 进行测验。

  • electron-sdk 封装

    云信 IM sdk N-Api 完结 node addon 的办法接入 native IM sdk,Napi::CallbackInfo 能够很简单的转化为 Json 格局数据,经过反射的办法来调用底层 native sdk 再合适不过了。

ts 代码节选

C++ 静态反射在网易云信 SDK 中的实践

js 代码节选

C++ 静态反射在网易云信 SDK 中的实践