技術乾貨 | C++ 靜態反射在網易雲信 SDK 中的實踐
導讀: 目前網 易 雲信的 IM SDK 支援全平臺,IM SDK 每次發版除了要針對新功能進行測試外,迴歸測試也佔了很大比重,只單純依靠人工測試,會出現許多問題。網易雲信的“ 自動化測試平臺”解放了大量的人力,本文將基於此,分享 C++ 靜態反射在雲信 SDK 中的應用實踐。
文| 郝魁
網易雲信資深 C++ 開發工程師
對話系統
目前網易雲信 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(http://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_1
index: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++ 編譯時 結構體欄位反射》
(http://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)
_In vok eStaticApi 可以理解為 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 / 45c6af3c98409b18a84451215d0bdd6e
NIMAuthService::login result:true
api-NIMAuthService::loginExtest_accid / 123456 / 45c6af3c98409b18a84451215d0bdd6e
nim::LoginCallback
params:
--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:small_blue_diamond: Callback value:\n\t\t -- Null or Undefined')
} else {
console.log('\t:small_blue_diamond: Callback value:\n\t\t', requestAck)
}
}
var _callback2 = (requestAck) => {
if (!requestAck && requestAck !== 0) {
console.log('\t:small_blue_diamond: Callback value:\n\t\t -- Null or Undefined')
} else {
console.log('\t:small_blue_diamond: Callback value:\n\t\t', requestAck)
}
}
var _result
if (_funParam.length === 0) {
console.log(':large_blue_diamond: -- API【', moduleName, '/', funName, '】\n\t:small_blue_diamond: callbackCount:', _cbCount, '\n\t:small_blue_diamond: 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(':large_blue_diamond: -- API【', moduleName, '/', funName, '】\n\t:small_blue_diamond: callbackCount:', _cbCount, '\n\t:small_blue_diamond: 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(':large_blue_diamond: -- API【', moduleName, '/', funName, '】\n\t:small_blue_diamond: callbackCount:', _cbCount, '\n\t:small_blue_diamond: Param1:\n\t\t', paramTwoOne, '\n\t:small_blue_diamond: 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:small_blue_diamond: Return value:\n\t\t -- Null or Undefined')
} else {
console.log('\t:small_blue_diamond: Return value:\n\t\t', JSON.stringify(_result))
}
}
作者簡介
郝魁,網易雲信資深 C++ 開發工程師,主要負責網易雲信 IM SDK 的開發、維護、重構等工作,擁有多年 C++ 客戶端開發經驗,現致力於跨平臺 C++ 開發。
參考資料
技術方案參考:現代 C++ 編譯時 結構體欄位反射
(http://zhuanlan.zhihu.com/p/88144082)
參考開原始碼:xpack/rttr
(http://github.com/xyz347/xpack/http://github.com/rttrorg/rttr)
使用到的開原始碼:Json
(http://github.com/nlohmann/json)
相關閱讀推薦
- “易 ”開源 | 簡單可信賴,GameSentry 正式開源
- 網易易盾 GameSentry 正式開源,做遊戲安全保障的尖兵利刃
- 技術乾貨 | 網易雲商元件體系建設
- 技術乾貨 | 網易雲商語音機器人對話管理測試自動化實踐
- 技術文章 | Android 模擬點選研究
- NLPCC 2022 NER 評測冠軍 | 網易雲商命名實體識別技術解讀
- Sanitizers 系列之 address sanitizer 原理篇
- Sanitizers 系列之 address sanitizer 用法篇
- 技術乾貨 | 給語音機器人裝上盾牌——限流
- 技術乾貨 | C 靜態反射在網易雲信 SDK 中的實踐
- 深度剖析「圈組」關係系統設計 | 「圈組」技術系列文章
- 技術乾貨 | TypeScript 進階指南,突破基本型別
- 技術乾貨 | Electron 外掛開發實踐
- 技術乾貨 | 超低延遲傳輸網路架構在元宇宙場景的應用
- 技術乾貨 | 網易雲信 QUIC 應用優化實踐
- 研發出了線上事故要不要績效 C?
- 技術乾貨 | IndexedDB 程式碼封裝、效能摸索以及多標籤支援
- 技術乾貨 | 基於 Prometheus 精準監控 & 報警實踐
- 深度剖析「圈組」訊息系統設計 | 「圈組」技術系列文章
- 技術乾貨 | 大資料洞察畫像自動化實踐