写点什么

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

作者:网易智企
  • 2022 年 6 月 16 日
  • 本文字数:12469 字

    阅读完需:约 41 分钟

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

对话系统


目前网易云信 IM SDK 支持全平台,IM SDK 每次发版除了要针对新功能进行测试外,回归测试也占了很大比重,随着 IM SDK 支持的平台越来越多,API 接口越来越丰富,每次需要回归测试的范围也在不断增加,如果以人工来完成,不仅效率低下,而且可能会产生漏测、误判等问题。为了提高回归效率、持续监控程序质量,云信在“质量保障体系”建设中投入了大量精力来搭建和完善“自动化测试平台”,“API 自动化测试”作为自动化测试平台的重要组件,为“单元测试”、“回归测试”、“每日构建”等功能提供了有力支撑,接入自动化测试平台非常有意义,其中“API 自动化测试”的主要流程如下图所示,本文就“根据 API 信息转为 API 调用”、“API 调用结果转换为用例执行结果数据”在桌面端(Windows、MacOSX、Linux)实现的关键技术进行讨论。



为什么要使用反射

下图所展示的内容是由“自动化测试平台”下发的用例信息(为了方便展示,字段有裁剪)。



其中 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(仅示例):


namespace nim {struct AuthInfo {std::string accid;std::string token;std::string appkey;};        struct AuthCallbackParam {std::string accid;bool result;int step;};using LoginCallback = std::function<void(const AuthCallbackParam&)>;struct NIMAuthService {static bool login(const AuthInfo& info) {std::cout << info.accid << " / " << info.token << " / " << info.appkey << std::endl;return true;}static void loginEx(const AuthInfo& info,const LoginCallback& callback) {std::cout << info.accid << " / " << info.token << " / " << info.appkey << std::endl;if (callback != nullptr) {callback(true);}}};}
复制代码


不使用反射机制,需要根据输入的 Json 数据来查找对应的 service,并找到对应的方法,把 Json 数据转换为对应的 C++ 结构,然后进行 API 调用,基中会涉及到大量的重复逻辑的判断。


struct ParameterConverter {static bool FromJsonData(const std::string& param, nim::AuthInfo& auth_info) {auto json_param = nlohmann::json::parse(param);if (json_param.is_object()) {if(auto it = json_param.find("accid");it != json_param.end())it->get_to(auth_info.accid);if (auto it = json_param.find("token"); it != json_param.end())it->get_to(auth_info.token);if (auto it = json_param.find("appkey"); it != json_param.end())it->get_to(auth_info.appkey);}return true;}};
bool InvokeSDKAPI(const std::string& serviceName,const std::string& functionName,const std::string param) {if (serviceName.compare("NIMAuthService") == 0) {if (functionName.compare("login") == 0) {nim::AuthInfo auth_info;if (ParameterConverter::FromJsonData(param, auth_info)) {nim::NIMAuthService::login(auth_info);}} ......} ......return true;}
复制代码


引入反射机制之后:



RefRegStruct(nim::AuthInfo, RefRegStructField(accid, "accid"), RefRegStructField(token, "token"), RefRegStructField(appkey, "appkey"));bool InvokeSDKAPI(const std::string& serviceName,const std::string& functionName,const std::string param) {auto res = SDKAPIRefManager::InvokeAPI({ serviceName, functionName }, { param });std::cout << serviceName << "::" << functionName << " result:" << res << std::endl;return true;}int main() {std::string param("{\"accid\":\"test_accid\",\"token\":\"123456\",\"appkey\":\"45c6af3c98409b18a84451215d0bdd6e\"}"); auth_info.accid = "test_accid"; RegApi("NIMAuthService", "login",&nim::NIMAuthService::login);auto res = SDKAPIRefManager::InvokeAPI({ "NIMAuthService", "login" }, { param });std::cout << "NIMAuthService::login result:" << res << std::endl;return 0;}
复制代码


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


引入反射的意义 :

对于统一、通用的接口描述,使用反射来实现从无类型数据构造运行时参数,查找 API 并完成调用。

  • 测试平台,完成 SDK 平台无关性、业务一致性测试。

  • 测试接入程序具备“接入便捷”,“兼容多版本 SDK”、“易维护”的特性。

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


反射的实现原理


对于 Java、Object-c、C# 的程序员来说”反射“是很常用的机制,但 C++ 出于性能及语言特性的考虑,并没有实现反射机制,C++ 虽然没有提供反射的实现,但强大的 C++ 语言可以很方便的实现反射机制。

为了方便进行转换使用的 Json 库为 nlohmann/json(https://github.com/nlohmann/json

可以先看下面示例代码:



struct Test {int index{1};std::string name{"test_1"};void printInfo() const {std::cout << "index:" << index << " name:" << name << std::endl;}};int main() {auto index_addr = &Test::index;auto name_addr = &Test::name;auto fun_print_addr = &Test::printInfo;Test test;test.index = 1;test.name = "test_1";test.printInfo();test.*index_addr = 2;test.*name_addr = "test_2";(test.*fun_print_addr)();return 0;int index{0}; }
复制代码


输出结果:

index:1 name:test_1index:2 name:test_2
复制代码


观察示例中的代码可以发现,通过取得类成员变量的地址来完成对具体类实例对象成员进行调用。

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,可以很方便的存储这些信息,形如:


std::tuple< std::tuple<TSField1,std::string/*field1_name*/>, std::tuple<TSField2,std::string/*field2_name*/>, std::tuple<TSField3,std::string/*field3_name*/>,......>;
复制代码


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

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

https://zhuanlan.zhihu.com/p/88144082


2. API 的元数据信息保存

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


映射关系如下:



所以有如下的定义:


/// 参数类型定义using function_param_t = std::string;/// 返回值定义using function_return_t = std::string;///  sdk-api名称定义,用于反射信息注册与查找struct _function_name {std::string service_name;std::string api_name;};using function_name_t = _function_name;using function_param_list_t = std::vector<function_param_t>;/// 类型擦除后的api格式定义using api_function_wrapper_t = std::function<function_return_t(const function_param_list_t&)>;/// api反射信息的聚合包装struct _api_wrapper {function_name_t function_name;api_function_wrapper_t api;};using api_wrapper_t = _api_wrapper;/// api反射信息的存储容器定义 using api_function_list_t = std::map<function_name_t, api_wrapper_t>;
复制代码


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。


/// 注册api反射信息template <typename TApi, TApi api,    typename std::enable_if_t<!std::is_member_function_pointer<TApi>::value,nullptr_t> = nullptr>static void RegisterApi(const std::string& service_name,const std::string& api_name) {    api_wrapper_t _api_wrapper;    _api_wrapper.function_name = { service_name,api_name };    _api_wrapper.api = MakeInvokeApi<TApi>(api);    RegisterApi(_api_wrapper);}static void RegisterApi(const api_wrapper_t& api) {    api_list[api.function_name] = api;}/// 生成类型擦除后的api元信息template <typename TApi,    typename std::enable_if_t<!std::is_member_function_pointer<TApi>::value,nullptr_t> = nullptr>static api_function_wrapper_t MakeInvokeApi(TApi api) {        return [api](const function_param_list_t& info) -> function_return_t {            return _InvokeStaticApi(info, api);    };}template <typename R, typename... Args,    typename std::enable_if<!std::is_void<R>::value, nullptr_t>::type = nullptr>static function_return_t _InvokeStaticApi(const function_param_list_t& info,R(*f)(Args...)) {    auto _tuple = JsonParam2Tuple<Args...>(info);    auto _value = TupleCall(f, _tuple);    return StructToJson(_value);}template<typename TStruct>static TStruct JsonToStruct(const std::string& json_param) {    auto json_obj = nlohmann::json::parse(json_param);    if (json_obj.is_object())        return json_obj.get<TStruct>();    return TStruct();}template<typename TStruct>static std::string StructToJson(const TStruct& param) {    return nlohmann::json(param).dump();}template <typename... Args, std::size_t... Is>static auto JsonParam2TupleImpl(const function_param_list_t& info,    const std::index_sequence<Is...>) {    return std::make_tuple(JsonToStruct<std::decay<Args>::type>(info[Is])...);}template <typename... Args>static auto JsonParam2TupleImpl(const function_param_list_t& info) {    return JsonParam2TupleImpl<Args...>(info, std::make_index_sequence<sizeof...(Args)>());}template <typename... TArgs>static auto JsonParam2Tuple(const function_param_list_t& info) {    return JsonParam2TupleImpl<TArgs...>(info);}template <typename TReturn, typename... TArgs, std::size_t... I>static TReturn TupleCallImpl(TReturn(*fun)(TArgs...),    const std::tuple<std::remove_reference_t<std::remove_cv_t<TArgs>>...>& tup,    std::index_sequence<I...>) {    return fun(std::get<I>(tup)...);}template <typename TReturn, typename... TArgs,    typename = typename std::enable_if<!std::is_member_function_pointer<    TReturn(*)(TArgs...)>::value>::type>static TReturn TupleCall(TReturn(*fun)(TArgs...),    const std::tuple<std::remove_reference_t<std::remove_cv_t<TArgs>>...>& tup) {    return TupleCallImpl(fun, tup,std::make_index_sequence<sizeof...(TArgs)>());}
复制代码


4. API 调用

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


static function_return_t InvokeAPI(    const function_name_t& api_name,     const function_param_list_t& param_list) {        auto it = api_list.find(api_name);        if (it != api_list.end())            return it->second.api(param_list);        return function_return_t();}
复制代码


5. 关于回调的处理

在 SDK 的模拟代码中有 NIMAuthService::loginEx 的定义,基参数列表是一个 LoginCallback,它是一个函数对象,无法通过 Json 来进行格式化,此时 loginEx 转换的 param_list[1](LoginCallback)仅相当于一个占位符,它的类型,可以在注册 API 时进行推导,然后通过 JsonToStruct 的特化来进行处理,生成可以与 SDK-API 兼容的回调对象,在生成的回调对象执行中,把回调结果通知出去。


特化及示例代码:


RefRegStruct(nim::AuthCallbackParam    , RefRegStructField(accid, "accid")    , RefRegStructField(step, "step")    , RefRegStructField(result, "result"));template <>nim::LoginCallback SDKAPIRefManager::JsonToStruct<nim::LoginCallback>(const function_param_t& value) {    return SDKAPIRefManager::MakeAPICallback(        "nim::LoginCallback",         ((nim::LoginCallback*)(nullptr))    );}int main() {    std::string param("{\"accid\":\"test_accid\",\"token\":\"123456\",\"appkey\":\"45c6af3c98409b18a84451215d0bdd6e\"}");    auth_info.accid = "test_accid";RegApi("NIMAuthService", "loginEx",nim::NIMAuthService::loginEx);    SDKAPIRefManager::InvokeAPI({ "NIMAuthService", "loginEx" }, { struct_text ,"LoginCallback"});    return 0;}RegApi("NIMAuthService", "login",&nim::NIMAuthService::login);
复制代码


对应的实现代码:


template <typename TTup, typename... Args, size_t... Is>static void TupleToCbArgsImpl(const TTup& tup,function_param_list_t& args,std::index_sequence<Is...>) {args = { StructToJson(std::get<Is>(tup))... };}template <typename TTup, typename... Args>static void TupleToCbArgsImpl( const TTup& tup,function_param_list_t& args) {TupleToCbArgsImpl<TTup, Args...>(tup, args, std::make_index_sequence<sizeof...(Args)>());}template <typename... TArgs>static function_param_list_t TupleToCbArgs(const std::tuple<TArgs...>& tup) {function_param_list_t args;TupleToCbArgsImpl<std::tuple<TArgs...>, TArgs...>(tup, args);return args;}template <typename TReturn, typename... TArgs>static std::function<TReturn(TArgs...)>MakeAPICallback(             const std::string& callback_name,              const std::function<TReturn(TArgs...)>* realcb) {             auto callback = [callback_name](TArgs... param) -> TReturn {                auto tup = std::make_tuple(std::forward<TArgs>(param)...);auto ret = TupleToCbArgs(tup);OnAPICallback(callback_name,ret);return TReturn();};return callback;}static void OnAPICallback(const std::string& callback_name, const function_param_list_t& param) {std::cout << callback_name << std::endl;std::cout << "params:" << std::endl;std::cout << "--begin" << std::endl;for (auto& it : param) {std::cout << it << std::endl;}std::cout << "--end" << std::endl;}
复制代码


回调的注册可以使用宏来完成:


#define CallbackDescription(Callback) ((Callback *)(nullptr))#define CallbackSpecialization(callback_name,Callback)             \template <>     \Callback SDKAPIRefManager::JsonToStruct<Callback>(                   \  const function_param_t &value) {                                   \return SDKAPIRefManager::MakeAPICallback(callback_name,              \    CallbackDescription(Callback));                                  \}
复制代码


示例代码总览:


namespace nim {struct AuthInfo {std::string accid;std::string token;std::string appkey;};struct AuthCallbackParam {std::string accid;bool result;int step;};using LoginCallback = std::function<void(const AuthCallbackParam& param)>;struct NIMAuthService {static bool login(const AuthInfo& info) {std::cout                            << "api-NIMAuthService::login"                            << info.accid << " / "                            << info.token << " / "                            << info.appkey << std::endl;return true;}static void loginEx(const AuthInfo& info,const LoginCallback& callback) {std::cout                            << "api-NIMAuthService::loginEx"                            << info.accid << " / "                            << info.token << " / "                            << info.appkey << std::endl;if (callback != nullptr) {AuthCallbackParam param;callback({ info.accid,true,3 });}}};}RefRegStruct(nim::AuthInfo, RefRegStructField(accid, "accid"), RefRegStructField(token, "token"), RefRegStructField(appkey, "appkey"));RefRegStruct(nim::AuthCallbackParam, RefRegStructField(accid, "accid"), RefRegStructField(step, "step"), RefRegStructField(result, "result"));CallbackSpecialization("nim::LoginCallback", nim::LoginCallback);int main() {std::string param("{\"accid\":\"test_accid\",\"token\":\"123456\",\"appkey\":\"45c6af3c98409b18a84451215d0bdd6e\"}");RegApi("NIMAuthService", "login",&nim::NIMAuthService::login);RegApi("NIMAuthService", "loginEx", &nim::NIMAuthService::loginEx);auto res = SDKAPIRefManager::InvokeAPI({ "NIMAuthService", "login" }, { param });std::cout << "NIMAuthService::login result:" << res << std::endl;SDKAPIRefManager::InvokeAPI({ "NIMAuthService", "loginEx" }, { param ,"LoginCallback"});return 0;}NIMAuthService::login result:true
复制代码


输出结果:


api-NIMAuthService::logintest_accid / 123456 / 45c6af3c98409b18a84451215d0bdd6eNIMAuthService::login result:trueapi-NIMAuthService::loginExtest_accid / 123456 / 45c6af3c98409b18a84451215d0bdd6enim::LoginCallbackparams:--begin{"accid":"test_accid","result":true,"step":3}--end
复制代码


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


反射信息的生成 


IM SDK 接提供了大量接口,在生成反射信息时,如果依靠人肉手撸代码的方式会存在以下几个问题:

  • 工作量巨大

  • 容易出错

  • API 改动,需要搜索出对应的反射信息,一一进行修改

所以我们引入了 libclang 来自动生成反射代码,基于 clang 的源到源转译工具可以参考我们的一篇分享文章《NeCodeGen:基于 clang 的源到源转译工具》

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

/** * @brief * 登录参数定义 */ReflectionDefinitionAndReg(nim_api_NIM_AuthInfo,nim_api::NIM_AuthInfo,    ///应用的appkey    appKey, "appKey",    ///登录账号    accid, "accid",    ///登录密码    token, "token");
/** * @brief * 登录结果回调参数定义 */ReflectionDefinitionAndReg(nim_api_NIM_LoginCBParam,nim_api::NIM_LoginCBParam, ///错误码 code, "code", ///当前登录步骤 step, "step", ///当前登录步骤描述 message, "message", ///是否为重新登录 relogin, "relogin");NE_GTAPI_CALLER::ApiObject NIMAuthService::Init(NE_GTAPI_CALLER::ApiEnv env, NE_GTAPI_CALLER::ApiObject exports) {
return InternalInit(TransClassName(NIMAuthService), env, exports, {
RegApi("registerKickout",&nim_api::AuthService::registerKickout),
RegApi("registerMultiSpot",&nim_api::AuthService::registerMultiSpot),
RegApi("registerDisconnect",&nim_api::AuthService::registerDisconnect),
RegApi("registerRelogin",&nim_api::AuthService::registerRelogin),
RegApi("login",&nim_api::AuthService::login),
RegApi("logout",&nim_api::AuthService::logout),
RegApi("kickOtherClient",&nim_api::AuthService::kickOtherClient),
RegApi("getLoginState",&nim_api::AuthService::getLoginState),

});}void NIMAuthService::RegisterNotifyCallback() {
RegisterSDKNotifyCallback("onKickout", GetSDKService(), &nim_api::AuthService::registerKickout); RegisterSDKNotifyCallback("onMultiSpot", GetSDKService(), &nim_api::AuthService::registerMultiSpot); RegisterSDKNotifyCallback("onDisconnect", GetSDKService(), &nim_api::AuthService::registerDisconnect); RegisterSDKNotifyCallback("onRelogin", GetSDKService(), &nim_api::AuthService::registerRelogin);
}
复制代码


应用场景 


  • 自动化测试

前面几个章节在介绍 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 代码节选


export interface NIM_AuthInfo {  appKey: string,  accountId: string,  token: string}export interface NIM_LoginCBParam {  code: number,  step: number,  message: string,  relogin: boolean}export interface AuthenticationAPI {  getParamRefValue(paramName:string) : string  login(authInfo: NIM_AuthInfo, callback: LoginCallback): void  logout(callback: LogoutCallback): void;  kickOtherClient(param: NIM_KickOtherClientRequestParam, callback: KickOtherClientCallback): void  getLoginState(): NIM_LoginState}class Authentication extends ServiceBase {  constructor(clientInstance: ClientInstance) {    super()    this._authService = new ServiceBase.nim.Authentication(clientInstance, this.emit.bind(this))  }  getParamRefValue(paramName:string) : string{    return this._authService.getParamRefValue(paramName)  }  login(authInfo: nim_api.NIM_AuthInfo, callback: nim_api.LoginCallback): void {    this._authService.login(authInfo, callback)  }
logout(callback: nim_api.LogoutCallback): void { this._authService.logout(callback) }
kickOtherClient(param: nim_api.NIM_KickOtherClientRequestParam, callback: nim_api.KickOtherClientCallback): void { this._authService.kickOtherClient(param, callback) }
getLoginState(): nim_api.NIM_LoginState { return this._authService.getLoginState() }
private _authService: nim_api.AuthenticationAPI}
复制代码


js 代码节选


funList: {        enter: {moduleName: '_chatroomService', funName: 'enter', funParam: ['NIM_ChatroomEnterParam'], cbCount: 1},        independentEnter: {moduleName: '_chatroomService', funName: 'independentEnter', funParam: ['NIM_ChatroomIndependentEnterParam'], cbCount: 1},        anoymityEnter: {moduleName: '_chatroomService', funName: 'anoymityEnter', funParam: ['NIM_ChatroomAnoymityEnterParam'], cbCount: 1},        exit: {moduleName: '_chatroomService', funName: 'exit', funParam: [], cbCount: 1},               getInfo: {moduleName: '_chatroomService', funName: 'getInfo', funParam: [], cbCount: 1},        updateInfo: {moduleName: '_chatroomService', funName: 'updateInfo', funParam: ['NIM_ChatroomInfoUpdateParam'], cbCount: 1}      }invokApi (moduleName, funName) {      var _funParam = this.activeFunList[funName].funParam      var _cbCount = this.activeFunList[funName].cbCount      var _callback = (requestAck) => {        if (!requestAck && requestAck !== 0) {          console.log('\t🔹 Callback value:\n\t\t -- Null or Undefined')        } else {          console.log('\t🔹 Callback value:\n\t\t', requestAck)        }      }      var _callback2 = (requestAck) => {        if (!requestAck && requestAck !== 0) {          console.log('\t🔹 Callback value:\n\t\t -- Null or Undefined')        } else {          console.log('\t🔹 Callback value:\n\t\t', requestAck)        }      }      var _result      if (_funParam.length === 0) {        console.log('🔷 -- API【', moduleName, '/', funName, '】\n\t🔹 callbackCount:', _cbCount, '\n\t🔹 Param:\n\t\t', 'No parameters required')        if (_cbCount === 0) {          _result = this.clientInstance[moduleName][funName]()        } else if (_cbCount === 1) {          _result = this.clientInstance[moduleName][funName](_callback1)        } else if (_cbCount === 2) {          _result = this.clientInstance[moduleName][funName](_callback1, _callback2)        } else {        }      } else if (_funParam.length === 1) {        var paramOne = JSON.parse(this.requestParam1)        console.log('🔷 -- API【', moduleName, '/', funName, '】\n\t🔹 callbackCount:', _cbCount, '\n\t🔹 Param:\n\t\t', paramOne)        if (_cbCount === 0) {          _result = this.clientInstance[moduleName][funName](paramOne)        } else if (_cbCount === 1) {          _result = this.clientInstance[moduleName][funName](paramOne, _callback1)        } else if (_cbCount === 2) {          _result = this.clientInstance[moduleName][funName](paramOne, _callback1, _callback2)        } else {        }      } else if (_funParam.length === 2) {        var paramTwoOne = JSON.parse(this.requestParam1)        var paramTwoTwo = JSON.parse(this.requestParam2)        console.log('🔷 -- API【', moduleName, '/', funName, '】\n\t🔹 callbackCount:', _cbCount, '\n\t🔹 Param1:\n\t\t', paramTwoOne, '\n\t🔹 Param2:\n\t\t', paramTwoTwo)        if (_cbCount === 0) {          _result = this.clientInstance[moduleName][funName](paramTwoOne, paramTwoTwo)        } else if (_cbCount === 1) {          _result = this.clientInstance[moduleName][funName](paramTwoOne, paramTwoTwo, _callback1)        } else if (_cbCount === 2) {          _result = this.clientInstance[moduleName][funName](paramTwoOne, paramTwoTwo, _callback1, _callback2)        } else {        }      } else {
} if (!_result && _result !== 0) { console.log('\t🔹 Return value:\n\t\t -- Null or Undefined') } else { console.log('\t🔹 Return value:\n\t\t', JSON.stringify(_result)) } }
复制代码


发布于: 刚刚阅读数: 3
用户头像

网易智企

关注

网易智企是网易旗下一站式企业服务提供商。 2022.05.09 加入

网易智企是网易旗下一站式企业服务提供商,包含网易易盾、网易云信、网易云商三大业务板块。

评论

发布
暂无评论
【网易云信】C++ 静态反射在网易云信 SDK 中的实践_c++_网易智企_InfoQ写作社区