Shiply Windows 远程资源 SDK 集成
一、隐私安全说明
Shiply 资源发布 Windows SDK
版本:1.2.17
更新时间:2024年12月25日
SDK介绍:为移动开发者提供专业的资源分发能力,帮助开发者动态下载远程资源文件。
服务提供方:深圳市腾讯计算机系统有限公司
接入指引:《Shiply资源发布SDK接入指引》
隐私保护规则:《Shiply资源发布SDK个人信息保护规则》
合规使用指南:《Shiply资源SDK合规使用指南》
前置概念:接入前请先阅读 通用概念,了解各平台共用的
appId/appKey/userId/deviceId/target/environment/资源加载策略 等核心概念,避免跨平台混淆。
二、SDK 整体架构
分层结构
Windows SDK 采用三层架构:
┌──────────────────────────────────────────────────────┐
│ C API 公开层 │
│ reshub_c.h / rdelivery_c_api.h │
│ (extern "C" 导出函数,DLL 边界安全) │
├──────────────────────────────────────────────────────┤
│ C API 桥接/实现层 │
│ reshub_c.cpp / reshub_service_c.cpp │
│ ResHubInstanceHolder (void* 句柄包装) │
│ ResHubDependencyInjector (依赖注入适配) │
├──────────────────────────────────────────────────────┤
│ C++ 核心层 │
│ ResHubCenter (单例, SDK 总入口) │
│ ├── ResHub (资源操作核心, 每个 App+Env 一个实例) │
│ │ ├── ResHubConfigFetchManager (配置拉取管理) │
│ │ │ └── rdelivery::RDeliveryService (底层引擎)│
│ │ ├── ResHubResLoader (单资源加载器) │
│ │ └── ResHubLocalResManager (本地资源管理) │
│ └── ResHubParam (全局初始化参数) │
└──────────────────────────────────────────────────────┘
核心类职责:
ResHubCenter(单例):SDK 唯一入口。initSDK()初始化全局参数,resHubWithAppId()按(appId + env)维度创建/缓存ResHub实例。ResHub:每个 App+Env 对应一个实例,负责所有资源操作(加载、锁定、预加载、删除、状态检查)。ResHubConfigFetchManager:每个ResHub拥有一个,内部创建独立的RDeliveryService实例(system_id=10010),负责拉取资源元数据(版本号、下载URL、MD5等)。
资源 SDK 与配置 SDK 的关系
两者底层使用同一个 rdelivery::RDeliveryService C++ 类,但是不同的实例,配置不同的 system_id:
┌─ ResHub (资源SDK) ─┐
│ system_id=10010 │
RDelivery C++ 核心 ──┤ ├── 资源元数据拉取 + 文件下载
(RDeliveryService) │ 内部自动管理 │
└────────────────────┘
┌─ 独立配置SDK ──────┐
│ system_id=10001 │
RDelivery C++ 核心 ──┤ ├── 开关/配置值拉取
(RDeliveryService) │ 用户自行创建管理 │
└────────────────────┘
ResHub 的 RDeliveryService 实例由其内部自动管理,用户不需要(也不应该)直接操作。
最小集成步骤(快速开始)
// 1. 引入头文件
#include "reshub/c_api/include/reshub_c.h"
#include "reshub/c_api/include/reshub_entities_c.h"
#include "reshub/c_api/include/reshub_callbacks_c.h"
// 2. 设置日志回调(可选,强烈建议)
setResHubLogImpl(myLogFunction);
// 3. 填充初始化参数(⚠️ 必须先 memset 清零)
struct ResHubParam_C params;
memset(¶ms, 0, sizeof(params));
params.app_version = "1.0.0";
params.qimei = "device_id";
params.device_type = "Windows PC";
params.system_version = "Windows 10";
params.platform = ResHubPlatformWindows_C;
params.environment = 0; // 0=Release
params.res_storage_path = "C:\\app\\reshub";
params.res_config_storage_path = "C:\\app\\reshub_cfg";
// 4. 初始化 SDK 中心
initResHubNative(¶ms);
// 5. 获取 ResHub 实例
struct ResHubAppInfo_C appInfo;
memset(&appInfo, 0, sizeof(appInfo));
appInfo.appId = "your_app_id";
appInfo.appKey = "your_app_key";
appInfo.env = "";
void* instance = getResHubNative(appInfo);
// 6. 加载资源
LoadCompleteBlock complete = { NULL, myCallback };
loadWithIdNative(instance, "my_res", NULL, &complete);
// 7. 释放(逆序)
releaseResHubNative(instance);
以上是最小可运行骨架,完整的字段表、API 参考和注意事项见后续章节。
三、为何使用 C 接口
Windows 平台使用 C API(非 C++ 类)提供 SDK 能力。这是为确保 ABI 兼容性的刻意设计,原因有三:
- C ABI 稳定,C++ ABI 不稳定:不同 MSVC 版本(VS 2019/2022/2025)编译的 C++ 库互相不兼容,符号修饰(name mangling)、异常处理、虚表布局都可能不同。C 接口无此问题。
- 运行时库(CRT)匹配:SDK 以
/MT(静态 CRT)编译。若使用/MD(动态 CRT)链接 C++ 接口,std::string、std::shared_ptr等类型的内存布局和分配器不同,导致堆损坏。C 接口的简单类型(指针、枚举、基本类型)不受 CRT 差异影响。 - 跨语言绑定:C 接口可直接用于其他语言(C#/Rust/Go)的 FFI 绑定,无中间层开销。
因此所有 SDK 头文件均以 _c.h 后缀暴露 C 函数和 C 结构体,禁止直接使用 SDK 内部的 C++ 类。
四、SDK 获取
预编译包(推荐)
下载 SDK 和 Demo:ShiplyCppSDK
| 参数 | 说明 |
|---|---|
version | SDK 版本,当前 1.2.17 |
variant | Tencent、Commercial、QQ |
ZIP 内部结构(以 Tencent x64 为例):
libshiply-Tencent-v1.2.17.zip
├── x64_Release/
│ ├── shiply.dll # 主动态库
│ ├── shiply.pdb # 调试符号
│ └── shiply.lib # 导入库
├── x86_Release/ # Win32 构建
├── arm64_Release/ # ARM64 构建
└── include/ # C 头文件
QQ 变体额外依赖:QQ 变体的认证链路不同,链接后还需引入 crypto.dll、ssl.dll(SDK 包内含)。
从源码构建
环境要求:
- Visual Studio 2022(MSVC v143 工具链)
- CMake 3.16+
- Windows 10 SDK 或更新
构建命令:
cd rdelivery-cpp
# 单架构构建
scripts\build_windows_sdk.bat x64 Tencent
# 全架构构建(x64, x86, arm64)
scripts\build_windows_sdk.bat all Commercial
产出位置:rdelivery-cpp/build_windows/{arch}_Release/
MSVC 编译标志:
| 标志 | 说明 |
|---|---|
/MT | 静态链接 CRT(发布方必须与消费方一致) |
/EHa | C++ 异常处理 + 结构化异常(SEH) |
/MP | 多进程并行编译 |
/Zi | 生成完整调试信息(PDB) |
依赖项:
| 依赖 | 获取方式 | 说明 |
|---|---|---|
| OpenSSL | libs/{triplet} 预编译 | 静态库,无需 vcpkg |
| ZLIB | libs/{triplet} 预编译 | 静态库 |
| MMKV | 源码编译(third_party/MMKV) | 随 SDK 构建一起编译 |
| minizip-ng | 源码编译 | Windows 中文路径解压必需 |
预编译依赖已随仓库提供在 libs/ 目录下,无需安装 vcpkg。
五、CMake 集成
链接 SDK
CMake 构建产出三个动态库(rdelivery-cpp/CMakeLists.txt):
| 目标 | 内容 | 推荐场景 |
|---|---|---|
shiply | reshub + rdelivery 全部源码合并(单体库) | 推荐接入,避免跨 DLL CRT 边界 |
rdelivery | 仅配置 SDK | 只需配置拉取 |
reshub | 资源 SDK(内部链接 rdelivery) | 只需资源功能 |
# 方式 A(推荐):链接单体 shiply 动态库(含 rdelivery + reshub)
target_link_libraries(your_target shiply)
target_include_directories(your_target PRIVATE ${SHIPLY_CPP_ROOT})
# 方式 B:仅需配置功能
target_link_libraries(your_target rdelivery)
target_include_directories(your_target PRIVATE ${SHIPLY_CPP_ROOT})
# 方式 C:仅需资源功能(内部仍链接 rdelivery)
target_link_libraries(your_target reshub)
target_include_directories(your_target PRIVATE ${SHIPLY_CPP_ROOT})
系统依赖
find_package(Threads REQUIRED)
target_link_libraries(your_target ${CMAKE_THREAD_LIBS_INIT})
六、初始化
#include "reshub/c_api/include/reshub_c.h"
#include "rdelivery/c_api/include/rdelivery_c_api.h"
// ⚠️ CRITICAL: 任何包含函数指针的结构体必须在填值前清零
// MSVC Debug 模式用 0xCC 填充未初始化栈内存,
// 结构体中残留的 0xCCCCCCCC 作为函数指针调用会立即崩溃。
// 1. 构造依赖注入句柄(可选,自定义网络/下载/解压时用)
ResHubDependencyInjectorHandle dep = ResHubDependencyInjector_Create();
// ResHubDependencyInjector_SetNetworkCallbacks(dep, &myNetworkVTable);
// ResHubDependencyInjector_SetDownloadCallbacks(dep, &myDownloadVTable);
// ResHubDependencyInjector_SetUnzipCallbacks(dep, &myUnzipVTable);
// ResHubDependencyInjector_SetAutoUnzipCallbacks(dep, &myAutoUnzipVTable);
// 2. 构造 ResHubParam_C(⚠️ 必须先 memset 清零)
struct ResHubParam_C params;
memset(¶ms, 0, sizeof(params)); // 必须!避免 MSVC Debug 0xCC 填充
params.app_version = "1.0.0";
params.system_version = "10.0";
params.device_type = "PC";
params.qimei = "deviceQimei";
params.platform = ResHubPlatformWindows_C; // 平台 = 9 (Windows)
params.environment = 0; // 0=Release, 1=Test, 2=Pre(预发布)
params.res_config_storage_path = "C:\\app\\reshub_cfg";
params.res_storage_path = "C:\\app\\reshub";
// ⚠️ enable_diff_patch 默认值为 TRUE,但 memset 会将其置为 FALSE
// 如果需要差分补丁,必须在 memset 之后显式设置:
params.enable_diff_patch = true; // 按需设置,默认 TRUE 是推荐值
// 3. 初始化 SDK 中心
initResHubNativeWithDependencyInjector(¶ms, dep);
// 4. 获取 App 实例
// ⚠️ 字段名为 camelCase: appId, appKey(非 snake_case)
struct ResHubAppInfo_C appInfo;
memset(&appInfo, 0, sizeof(appInfo)); // MSVC Debug 安全做法
appInfo.appId = "your_app_id"; // 注意:appId 非 app_id
appInfo.appKey = "your_app_key"; // 注意:appKey 非 app_key
appInfo.env = ""; // logicEnv,空字符串表示默认环境
appInfo.target = 0; // PullTarget: 0=Project, 1=App, 2=Both
void* instance = getResHubNative(appInfo);
配置 SDK 补充初始化
如果单独使用配置 SDK(RDelivery),Windows 上必须在创建实例后显式初始化结构体:
// 创建配置服务
RDConfig config;
RDConfig_Init(&config); // 必须先 Init 清零函数指针
// ... 填入 config 字段 ...
RDEventListener listener;
RDEventListener_Init(&listener); // 必须先 Init 清零函数指针
// ... 填入 listener 回调 ...
RDConfig_Init() 和 RDEventListener_Init() 内部执行 memset(ptr, 0, sizeof(*ptr)),避免 MSVC Debug 的 0xCC 填充问题。
七、ResHubParam_C 完整字段表
真值源:
reshub_entities_c.h。结构体包含 24 个字段,分类如下。
必填字段
| 字段 | 类型 | 说明 |
|---|---|---|
app_version | const char* | 应用版本号 |
qimei | const char* | 设备唯一标识 |
device_type | const char* | 设备型号 |
system_version | const char* | 操作系统版本 |
platform | int | 平台枚举,Windows 固定 ResHubPlatformWindows_C(= 9) |
可选字段
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
rdm_test | bool | false | Debug 包标记,影响测试环境路由 |
res_storage_path | const char* | SDK 默认路径 | 资源下载存储目录 |
res_config_storage_path | const char* | SDK 默认路径 | 资源配置持久化目录 |
local_preset_res_path | const char* | NULL | 预置资源路径(打包进安装目录的资源) |
multi_process_mode | bool | false | 多进程模式,开启后跨进程共享配置 |
is_main_process | bool | false | 与 multi_process_mode 配合,标记主进程 |
environment | int | 0 | 物理环境:0=Release, 1=Test, 2=Pre(预发布) |
config_update_mode | int | 参见枚举 | 配置更新策略(按位掩码) |
config_update_interval | int | SDK 默认值 | 配置定时更新间隔(秒) |
custom_server_url | const char* | NULL | 独立部署域名,NULL 使用默认域名 |
variant_map | const char* | NULL | 业务变体映射,用于 AB 实验路由 |
高级字段
| 字段 | 类型 | 默认 | 说明 |
|---|---|---|---|
enable_diff_patch | bool | TRUE | 差分补丁。⚠ 默认 TRUE 但 memset 变为 FALSE,需显式设置 |
enable_preset_res_validation | bool | false | 预置资源 MD5 校验(初始化阶段耗时) |
fetch_project_when_app_only | bool | false | 仅拉取 App 配置时是否同时拉取项目配置 |
config_store_suffix | const char* | NULL | 配置存储后缀,多账户隔离用 |
代理字段
| 字段 | 类型 | 说明 |
|---|---|---|
proxy_host | const char* | 代理服务器地址 |
proxy_port | int | 代理服务器端口 |
proxy_username | const char* | 代理认证用户名 |
proxy_password | const char* | 代理认证密码 |
其他
| 字段 | 类型 | 说明 |
|---|---|---|
dev_manufacturer | const char* | 设备制造商(如 "Dell"、"Lenovo") |
八、资源加载 API
三套语义(Lock / Latest / Realtime Latest):
| C 函数 | 语义 | 说明 |
|---|---|---|
resWithIdNative(instance, resId, needValidate) | Lock 同步 | 本地锁定版本,不发网络 |
latestResWithIdNative(instance, resId, needValidate) | Latest 同步 | 本地最新版本 |
loadWithIdNative(instance, resId, progress, completed) | Lock 异步 | 锁定版本,可能触发网络 |
loadLatestWithIdNative(instance, resId, progress, completed) | Latest 异步 | 本地配置中最新 |
loadRealtimeLatestWithIdNative(instance, resId, progress, completed) | Realtime 异步 | 强制实时请求配置 |
batchLoadWithIdsNative(instance, resIds, progress, completed) | 批量 Lock | |
batchLoadLatestWithIdsNative(instance, resIds, progress, completed) | 批量 Latest | |
batchLoadWithSceneIdNative(instance, sceneId, progress, completed) | 场景 Lock | 按场景 ID 批量 |
batchLoadLatestWithSceneIdNative(instance, sceneId, progress, completed) | 场景 Latest | |
preloadLatestWithIdNative / prebatchLoadLatestWithIdsNative | 预加载 | 受 CDN 流量与拦截规则限制 |
回调类型(reshub_callbacks_c.h):LoadProgressBlock、LoadCompleteBlock、BatchLoadProgressBlock、BatchLoadCompleteBlock、AllConfigsCompletionBlock、UpdateConfigsCompletionBlock。
每个 Block 结构体包含两个字段:
| 字段 | 类型 | 说明 |
|---|---|---|
callbackRef | void* | 用户透传上下文(调用方传入,回调中原样回传) |
callback | 函数指针 | 实际的回调函数(具体签名见对应 Block 类型定义) |
示例 — LoadProgressBlock:
LoadProgressBlock progress;
progress.callbackRef = this; // 透传上下文
progress.callback = [](void* ctx, int64_t received, int64_t total) {
// received: 已下载字节数, total: 总字节数
};
LoadCompleteBlock / BatchLoadProgressBlock / BatchLoadCompleteBlock 结构相同,仅 callback 签名不同(见 reshub_callbacks_c.h)。MSVC Debug 下必须初始化后使用。
典型使用流程
// 加载资源(异步,回调)
LoadProgressBlock progress = { NULL, MyProgressCallback };
LoadCompleteBlock completed = { NULL, MyCompletedCallback };
struct ResHubModel_C* m = loadWithIdNative(instance, "my_res", &progress, &completed);
// 同步读取本地
struct ResHubModel_C* local = resWithIdNative(instance, "my_res", true);
// 释放返回对象(必须!跨 DLL)
releaseResHubModelNative(local);
// 上报
reportResDownloadedNative(instance, "my_res", "taskId", 1); // version 参数类型为 int64_t
// 释放实例
releaseResHubNative(instance);
ResHubDependencyInjector_Destroy(dep);
九、配置拉取与管理
| C 函数 | 说明 |
|---|---|
updateAllConfigsWithCompletedNative(instance, completed) | 手动全量拉取配置 |
updateConfigWithKeysNative(instance, resIds, completed) | 按 key 拉取配置 |
latestConfigWithId(instance, resId) | 同步获取远程最新配置(不下载) |
fetchedResConfigWithIdNative(instance, resId) | 同步本地配置(不网络) |
fetchResConfigWithIdNative(instance, resId, completed) | 异步拉取配置(不下载) |
allReshubConfigDictNative(instance) | 所有远程配置 |
十、状态/校验/删除/取消
| C 函数 | 说明 |
|---|---|
getStatusNative(instance, resId) | 资源可用状态 |
isResFileValidNative(instance, model) | MD5 校验 |
deleteResNative(instance, resId) | 删除单个资源 |
deleteAllNative(instance) | 删除全部 |
cancelDownloadWithIdNative(instance, resId) | 取消下载 |
十一、上报
| C 函数 | 说明 |
|---|---|
reportResDownloadedNative(instance, resId, taskId, version) | 上报下载 |
reportResLoadedNative(instance, resId, taskId, version) | 上报加载 |
reportResPulledNative(instance, resId, taskId, version) | 上报拉取 |
setCustomPropertyValueNative(instance, value, key) | 更新自定义属性 |
reportToShiplyNative(instance, eventName, extInfo) | 上报自定义事件 |
代理
setProxyConfigNative(ProxyConfig_C* config) — 动态设置代理,传 NULL 清除。
十二、回调与监听
Windows ResHub C API 通过 reshub_callbacks_c.h 提供 6 个全局级回调设置函数:
// 日志回调
setResHubLogImpl(myLogFunction); // typedef int (*ResHubLog)(int level, const char *content);
// 资源生命周期回调
setOnResFirstLoaded(onResFirstLoaded); // void (*OnResFirstLoaded)(const char *resId, ResHubModel_C *model)
setOnResRefreshed(onResRefreshed); // void (*OnResRefreshed)(const char *resId, ResHubModel_C *model)
// 配置变更回调
setOnResConfigInfoPullTypeAllFinished(onFinished); // void (*)(ResHubModelDict *dict)
setOnConfigDeleted(onConfigDeleted); // void (*OnConfigDeleted)(const StringArray *resIds)
// 版本控制
setGetMinVersionDelegate(delegate); // int (*)(ResHubAppInfo_C *, const char *resId)
这些回调仅支持 set 操作(无 remove 对应函数)。如需清除,再次调用并传入 NULL 即可。所有回调必须在 initResHubNative 之前设置。
线程注意事项:回调执行线程由 ResHubParam_C.callbackOnMainThread 控制(默认值:false,即后台线程触发)。监听器在触发事件时所在的线程上执行。C API 不提供显式的线程安全保证。
十三、依赖注入
资源 SDK 依赖注入句柄
ResHubDependencyInjectorHandle ResHubDependencyInjector_Create();
void ResHubDependencyInjector_Destroy(handle);
void ResHubDependencyInjector_SetNetworkCallbacks(handle, const RDNetworkVTable_C*);
void ResHubDependencyInjector_SetDownloadCallbacks(handle, const RDDownloadVTable_C*);
void ResHubDependencyInjector_SetUnzipCallbacks(handle, const RDUnzipVTable_C*);
void ResHubDependencyInjector_SetAutoUnzipCallbacks(handle, const RDAutoUnzipVTable_C*);
未设置的模块走 SDK 默认实现。
配置 SDK 依赖注入
配置 SDK 的 DependencyInjector 结构体含:logger(ILog)、network(INetwork)、kvFactoryImpl(RAFTKVStorageFactoryProtocol)、rsa(IRSA)、aes(IAES)。
RDDependencyInjector_Create() / RDDependencyInjector_Destroy(handle)
RDDependencyInjector_SetNetworkCallbacks(handle, networkCallbacks)
十四、配置 SDK C API(rdelivery_c_api.h)
C 接口供 NAPI 桥接与跨语言绑定使用:
// ——实例生命周期——
RDService_Create() / RDService_Destroy(service)
// ——初始化(ktContext 为回调透传上下文)——
RDService_Init(service, db_root_path, config, ktContext, logCallback)
RDService_InitWithDependencyInjector(service, db_root_path, config, depInjector, ktContext, logCallback)
// ——读取(异步,回调返回)——
RDService_GetRDeliveryDataByKey(service, key, ktContext, OnGetDataResultCallback)
RDService_GetRDeliveryAllDataMap(service, ktContext, OnGetDataMapCallback)
// ——读取(同步,直接返回)——
RDService_SyncGetRDeliveryDataByKey(service, key)
RDService_SyncGetRDeliveryAllDataMap(service)
// ——远端拉取——
RDService_RequestFullRemoteData(service, custom_properties_json, ktContext, OnRequestCallback)
RDService_RequestBatchRemoteDataByScene(service, scene_id, custom_properties_json, ktContext, OnRequestCallback)
RDService_RequestBatchRemoteDataByScenes(service, scene_ids_json, custom_properties_json, ktContext, OnRequestCallback)
RDService_RequestSingleRemoteDataByKey(service, key, custom_properties_json, ktContext, OnRequestCallback)
RDService_RequestRemoteDataByKeys(service, keys_json, custom_properties_json, ktContext, OnRequestCallback)
// ——事件监听——
RDService_AddEventListener(service, listener) / RDService_RemoveEventListener(service, listener)
// ——用户/环境切换——
RDService_SwitchUserId(service, user_id, ktContext, OnCallback)
RDService_SwitchEnvironment(service, env, ktContext, OnCallback)
RDService_SwitchHostEnvironment(service, env, ktContext, OnCallback) // 仅 TAB 使用
// ——TAB 与代理——
RDService_SetTabBizSettings(service, settings)
RDService_SetProxyConfig(service, config)
// ——工具——
RDService_GetSDKVersion(service)
// ——结构体初始化(C 调用前必须先 Init,否则函数指针字段为未定义值)——
RDTabBizSettings_Init(bizSettings) / RDConfig_Init(config) / RDEventListener_Init(listener)
枚举(type_define.h):RDPlatform(9=Win,10=Mac,11=NodeServer,12=VisionOS,13=Harmony,14=HarmonyPad)、RDSwitch、RDValueType、RDHostEnvironment(Release/Test/Pre)、RDPullConfigType、RDPullType(0=Unknown,1=Deprecated,2=Group,3=Config,4=All)。
RDeliveryConfig 核心字段
| 类别 | 字段 | 说明 |
|---|---|---|
| 必填 | app_id、app_key | 应用鉴权 |
| 用户 | user_id(guid) | 用户标识 |
| 设备 | device_id(qimei)、devModel、devManufacturer | 设备标识与型号 |
| 系统 | platform(RDPlatform)、os_version、sdk_version | 平台/系统/SDK 版本 |
| 环境 | logic_environment、hostEnvironment、bundle_id | 逻辑环境、物理环境、包名 |
| 自定义 | custom_properties、language、is_debug_package | 自定义属性、语言、调试标记 |
| 更新 | update_strategy、update_interval | 更新策略与间隔 |
| 拉取 | system_id、target(项目/App)、fixed_after_hit_keys | 拉取目标与固化 key |
| 存储 | configStoreSuffix、multiProcessMode、configStoreCryptKey | 存储隔离与加密 |
| TAB | tab_biz_settings(RDTabBizSettings) | TAB 模式场景与参数 |
| 网络 | custom_server_url、proxy_config(ProxyConfig) | 独立部署域名、代理 |
| 加密 | rsa_impl(IRSA)、aes_impl(IAES) | 注入的加解密实现 |
RDEventListener 结构体字段
| 类别 | 字段 | 说明 |
|---|---|---|
| 回调 | OnDataInitComplete | 本地数据初始化完成 |
| 回调 | OnDataAdd | 数据新增 |
| 回调 | OnDataChange | 数据变更 |
| 回调 | OnBizDataUpdate | 业务数据更新 |
| 回调 | OnAllDataUpdated | 全量数据更新完成 |
| 回调 | OnDataDelete | 数据删除 |
| 上下文 | OnDataInitCompleteKtContext 等 | 各回调对应的透传上下文指针 |
线程安全:C API(RDService_*)的 listener 列表无内部同步 — AddEventListener / RemoveEventListener 需在同一线程调用或外部加锁同步。
十五、生命周期管理
释放顺序
释放必须在初始化方向逆序执行:
// 1. 先释放 App 实例
releaseResHubNative(instance);
// 2. 再销毁依赖注入句柄
ResHubDependencyInjector_Destroy(dep);
RDDependencyInjector_Destroy(dep);
RDService_Destroy(service);
跨 DLL 安全释放
SDK 通过自身堆分配内存,必须用 SDK 提供的 release 函数释放。禁止使用 free() 或 delete 操作 SDK 返回的对象。
| SDK 分配的对象 | 必须使用的释放函数 |
|---|---|
ResHubModel_C* | releaseResHubModelNative(model) |
ResHubConfigItem_C* | releaseResHubConfigItemNative(item) |
ResHubModelDict | releaseResHubModelDictNative(dict) |
ResHubConfigItemDict | releaseResHubConfigItemDictNative(dict) |
ResHubErrorDict | releaseResHubErrorDictNative(dict) |
十五、版本检测
// 配置 SDK 版本(资源 SDK 复用同一底层版本)
const char* version = RDService_GetSDKVersion(service);
// 返回 "1.2.17"
建议在初始化完成后立即调用,日志记录或运行时校验使用的 SDK 版本是否匹配预期。
十七、自定义解压注入(7z 等非 zip 格式)
SDK 默认解压实现(ResHubFileImpl)仅支持 zip 格式(基于 minizip/zlib)。当资源为 7z 等非 zip 格式时,需要业务方通过 ResHubDependencyInjector_SetUnzipCallbacks 注入自有解压库。
注意:SDK 的自动解压判断层(ResHubAutoUnzipMediator)已默认将 .7z 后缀的资源标记为「需要解压」,注入的解压实现会被 ResHubUnzipProcessor 自动调用,无需修改框架代码。
// 1. 实现解压回调(示例:调用业务自有 7z 库)
int MyUnzipImpl(void* ctx, const char* path, const char* dest,
int overwrite, const char* password, const char** error_msg) {
int ret = My7zExtract(path, dest, password, overwrite != 0);
if (ret != 0) {
*error_msg = "7z extraction failed";
}
return ret;
}
// 2. 构造 VTable 并注入
RDUnzipVTable_C unzipVTable;
memset(&unzipVTable, 0, sizeof(unzipVTable)); // MSVC Debug 必须清零
unzipVTable.impl_context = NULL; // 业务自定义上下文,可为 NULL
unzipVTable.UnzipFile = MyUnzipImpl;
ResHubDependencyInjectorHandle dep = ResHubDependencyInjector_Create();
ResHubDependencyInjector_SetUnzipCallbacks(dep, &unzipVTable);
| 参数 | 说明 |
|---|---|
path (in) | 压缩包文件路径(SDK 下载的资源文件) |
destination (in) | 解压目标目录 |
overwrite (in) | 是否覆盖已存在文件:1=覆盖,0=不覆盖 |
password (in) | 解压密码,无密码时为 NULL |
error_msg_out (out) | 失败时写入错误描述,仅需在函数返回前保持有效 |
| 返回值 | 0=成功,非 0=业务自定义错误码 |
传入 NULL 或 UnzipFile == NULL 的 VTable 会清除自定解压实现,回退到 SDK 默认 zip 解压。
十八、自定义解压判断注入
SDK 默认按文件后缀自动判断是否需要解压(.zip / .7z)。当业务使用自定义压缩格式(如 .dat、.bin 等),需要让 SDK 对其执行解压流程时,通过 ResHubDependencyInjector_SetAutoUnzipCallbacks 注入自定义判断逻辑:
// 1. 实现判断回调
int MyNeedUnzip(void* ctx, const char* resId, const char* downloadUrl,
const char* fileExtra, int noNeedUnZip) {
if (noNeedUnZip) return 0; // 配置明确标记不解压
if (strstr(downloadUrl, ".dat")) return 1; // 自定义格式需要解压
return 0; // SDK 对 zip/7z 仍会兜底判断
}
// 2. 构造 VTable 并注入
RDAutoUnzipVTable_C autoUnzipVTable;
memset(&autoUnzipVTable, 0, sizeof(autoUnzipVTable));
autoUnzipVTable.impl_context = NULL;
autoUnzipVTable.NeedUnzip = MyNeedUnzip;
ResHubDependencyInjectorHandle dep = ResHubDependencyInjector_Create();
ResHubDependencyInjector_SetAutoUnzipCallbacks(dep, &autoUnzipVTable);
| 参数 | 说明 |
|---|---|
resId (in) | 资源 ID |
downloadUrl (in) | 下载地址(可用于检查文件后缀) |
fileExtra (in) | 扩展信息 |
noNeedUnZip (in) | 配置中标记的不需要解压标志(1=不需要) |
| 返回值 | 非 0=需要解压,0=不需要 |
注意:业务回调返回非 0 时,即使后缀不是 zip/7z,ResHubAutoUnzipMediator 也会将该资源标记为「需要解压」,随后进入解压流程(调用 fileImpl,可以是 SetUnzipCallbacks 注入的自定义解压器)。
传入 NULL 或 NeedUnzip == NULL 的 VTable 会清除自定义判断,回退到 SDK 内置 zip/7z 后缀判断。
十九、重要注意事项
1. MSVC Debug 0xCC 填充(最关键)
MSVC Debug 模式将未初始化的栈内存填充为 0xCC。ResHubParam_C、ResHubAppInfo_C、RDConfig、RDEventListener 等含函数指针的结构体若不清零,0xCCCCCCCC 作为函数指针调用会直接 Access Violation。务必 memset(&s, 0, sizeof(s)) 或调用配套的 _Init() 函数。
// 必须遵守的初始化规则
RDEventListener_Init(&listener);
RDConfig_Init(&config);
RDTabBizSettings_Init(&settings);
ResHubParam_C params;
memset(¶ms, 0, sizeof(params));
2. enableDiffPatch 默认值陷阱
字段实际默认值为 TRUE,但标准初始化流程 memset(¶ms, 0, ...) 会将其置为 FALSE。如需差分补丁功能,必须在 memset 之后显式设置:
params.enable_diff_patch = true;
3. /MT 静态 CRT 一致性
SDK 以 /MT 编译。消费者项目也必须使用 /MT(非 /MD)。CRT 类型不匹配会导致 std::string 内存在不同堆之间泄漏/损坏,表现可能是崩溃、堆断言失败或静默数据损坏。
4. 跨 DLL 内存管理
SDK 从 DLL 内部分配内存的对象必须通过 SDK 释放函数归还。绝对禁止 free() 或 delete。
5. ResHubAppInfo_C 字段名
appId、appKey 为 camelCase,不是 app_id、app_key。填错字段名会导致编译器报错。
6. Windows 路径分隔符
正斜杠(/)和反斜杠(\)在 C API 中均有效。推荐使用正斜杠或双反斜杠(\\)以避免转义问题。
7. 中文路径解压
Windows 使用 minizip-ng 替代标准 zlib 解压以支持中文路径。构建 SDK 时此依赖自动处理,消费者无需额外配置。
8. QQ 变体额外 DLL
使用 QQ 变体时需额外部署 crypto.dll 和 ssl.dll,否则运行时加载失败。
9. 初始化先后顺序
先调用 initResHubNativeWithDependencyInjector 初始化 SDK 中心,再调用 getResHubNative 获取 App 实例。顺序颠倒返回 NULL。
10. 链接单体 shiply 库
推荐链接 shiply(含 rdelivery + reshub),避免分别链接 rdelivery/reshub 时的跨 DLL CRT 问题。即使不直接使用配置功能,单体库也更安全。