Zlib三方库的HarmonyOS适配(HAR)

📅 2026/7/8 2:10:30
Zlib三方库的HarmonyOS适配(HAR)
Zlib三方库HarmonyOS适配HAR背景知识首先需要知道Native、NAPI、SDK、SDK、三方库。这里不作解释参考如果不知道CMakeList.txt、napi_init.cpp、Index.d.ts等一定要看鸿蒙Native C入门_native c web 开发入门-CSDN博客其次还需要知道什么是HAP什么是HAR1、HAPHAPHarmony Ability Package是应用安装和运行的基本单元。HAP包是由代码、资源、三方库、配置文件等打包生成的模块包工程里对应Ability 类型 Module新建项目默认生成的模块就是 HAP。两类 HAPEntry HAP 入口主模块应用的主模块作为应用的入口提供了应用的基础功能Feature HAP 特性分包创建方式应用的动态特性模块作为应用能力的扩展可以根据用户的需求和设备类型进行选择性安装。HAP核心能力HAP中支持添加UIAbility组件或ExtensionAbility组件以及pages页面HAP中支持引用HAR或HSP共享包2、HARHAR不支持在设备上单独安装或运行只能作为应用模块的依赖项被引用创建方式Empty Ability HAP应用业务模块Native C 原生 C 开发的业务 HAPShared Library HSP动态共享包Static Library HAR静态共享包特点支持应用内共享也可以作为二方库SDK、三方库SDK发布后供其他应用使用。作为二方库SDK发布到OHPM私仓供公司内部其他应用使用。作为三方库SDK发布到OHPM中心仓供其他应用使用。图片中也写了用于共享代码、C库等这样我们适配好的Zlib就可以直接打包成HAR分享给需要的人。工作步骤1.下载Zlib第三方库并放到har模块中已经资源绑定在文章顶部展示。官方链接zlib Home Site第三个就是zip。解压后放到这里在har模块中的cpp里新建目录zlib2.检查解压后的zlib库可以看到里面就有一个自带的CMakeLists.txt这一点要记住我们后续会利用它。另外库中有许多函数但是我们只需要压缩和解压函数就可以了其他的如流式压缩、校验值计算等。第一步我们先看主头文件zlib.h所有规范的 C 语言三方库对外暴露的全部函数、宏、类型都会写在主头文件里并且自带功能注释。查找方法compress注释翻译原文中文翻译Compresses the source buffer into the destination buffer.将源缓冲区中的数据压缩至目标缓冲区。sourceLen is the byte length of the source buffer.sourceLen为源缓冲区的字节长度。Upon entry, destLen is the total size of the destination buffer,调用函数时destLen表示目标缓冲区的总容量which must be at least the value returned by compressBound(sourceLen).该容量必须不小于compressBound(sourceLen)的返回值。Upon exit, destLen is the actual size of the compressed data.函数执行结束后destLen会被更新为压缩后数据的实际字节大小。compress() is equivalent to compress2() with a level parameter of Z_DEFAULT_COMPRESSION.compress()等价于传入默认压缩级别参数Z_DEFAULT_COMPRESSION的compress2()。compress returns Z_OK if success,compress返回值规则执行成功返回Z_OKZ_MEM_ERROR if there was not enough memory,内存分配失败返回Z_MEM_ERRORZ_BUF_ERROR if there was not enough room in the output buffer.输出缓冲区空间不足返回Z_BUF_ERROR。uncompress注释翻译原文中文翻译Decompresses the source buffer into the destination buffer.将源缓冲区中的数据解压至目标缓冲区。sourceLen is the byte length of the source buffer.sourceLen为源缓冲区的字节长度。On entry, *destLen is the total size of the destination buffer,调用函数时*destLen表示目标缓冲区的总容量which must be large enough to hold the entire uncompressed data.该容量必须足以容纳完整的解压后数据。(The size of the uncompressed data must have been saved previously by the compressor解压后数据的原始大小必须由压缩端提前记录and transmitted to the decompressor by some mechanism outside the scope of this compression library.)并通过本压缩库职责范围之外的方式传递给解压端。On exit, *destLen is the actual size of the uncompressed data.函数执行结束后*destLen会被更新为解压后数据的实际字节大小。uncompress returns Z_OK if success,uncompress返回值规则执行成功返回Z_OKZ_MEM_ERROR if there was not enough memory,内存分配失败返回Z_MEM_ERRORZ_BUF_ERROR if there was not enough room in the output buffer,输出缓冲区空间不足返回Z_BUF_ERRORor Z_DATA_ERROR if the input data was corrupted or incomplete.输入数据损坏、不完整或格式非法返回Z_DATA_ERROR。In the case where there is not enough room,当出现缓冲区空间不足的情况时uncompress() will fill the output buffer with the uncompressed data up to that point.uncompress()会把已解压出的数据写入缓冲区直到缓冲区被填满为止。3.编辑我们自己的CMakeLists.txt# the minimum version of CMake. cmake_minimum_required(VERSION 3.5.0) project(myNpmLib) set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) if(DEFINED PACKAGE_FIND_FILE) include(${PACKAGE_FIND_FILE}) endif() include_directories(${NATIVERENDER_ROOT_PATH} ${NATIVERENDER_ROOT_PATH}/include ${NATIVERENDER_ROOT_PATH}/zlib) add_subdirectory(zlib) add_library(library SHARED napi_init.cpp) target_link_libraries(library PUBLIC libace_napi.z.so zlib)注意add_subdirectory(zlib)这里会调用zlib自带的CMakeLists。txt相当于把zlib当做一个独立子项目编译生成一个叫 zlib的库目标然后target_link_libraries(library PUBLIC libace_napi.z.so zlib)链接到我们的原生库上。总得来说就是调用 zlib 自带的构建脚本把 zlib 编译成独立库再链接到我们的liblibrary.soadd_subdirectory target_link4.编辑napi_init.cpp直接看代码我在里面加有详细注释。核心逻辑接收 ArkTS 传入的字符串 → 转为 C 字符数组 → 调用 zlib 压缩 → 将二进制结果安全回传给 ArkTS#include napi/native_api.h #include string #include vector extern C { #include zlib/zconf.h #include zlib/zlib.h } // ---- base64 编码/解码 ---- static const char kBase64Table[] ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/; static std::string base64_encode(const unsigned char *data, size_t len) { std::string out; out.reserve(((len 2) / 3) * 4); for (size_t i 0; i len; i 3) { unsigned char b0 data[i]; unsigned char b1 (i 1 len) ? data[i 1] : 0; unsigned char b2 (i 2 len) ? data[i 2] : 0; out.push_back(kBase64Table[b0 2]); out.push_back(kBase64Table[((b0 0x03) 4) | (b1 4)]); out.push_back((i 1 len) ? kBase64Table[((b1 0x0f) 2) | (b2 6)] : ); out.push_back((i 2 len) ? kBase64Table[b2 0x3f] : ); } return out; } static std::vectorunsigned char base64_decode(const std::string input) { static int decode_table[256] {}; static bool table_ready false; if (!table_ready) { for (int i 0; i 256; i) decode_table[i] -1; for (int i 0; i 64; i) decode_table[(unsigned char)kBase64Table[i]] i; table_ready true; } std::vectorunsigned char out; out.reserve((input.size() / 4) * 3); int val 0, valb -8; for (unsigned char c : input) { if (c ) break; int d decode_table[c]; if (d -1) continue; val (val 6) d; valb 6; if (valb 0) { out.push_back((val valb) 0xFF); valb - 8; } } return out; } static napi_value Add(napi_env env, napi_callback_info info) { size_t argc 2; napi_value args[2] {nullptr}; napi_get_cb_info(env, info, argc, args, nullptr, nullptr); napi_valuetype valuetype0; napi_typeof(env, args[0], valuetype0); napi_valuetype valuetype1; napi_typeof(env, args[1], valuetype1); double value0; napi_get_value_double(env, args[0], value0); double value1; napi_get_value_double(env, args[1], value1); napi_value sum; napi_create_double(env, value0 value1, sum); return sum; } // 封装压缩函数 static napi_value zlibCompress(napi_env env, napi_callback_info info) { // ① 获取 ArkTS 传过来的参数 size_t argc 1; napi_value args[1] {nullptr}; napi_get_cb_info(env, info, argc, args, nullptr, nullptr); /* 官方原型 NAPI_EXTERN napi_status napi_get_cb_info(napi_env env, napi_callback_info cbinfo, size_t* argc, // ⭐ 参数个数输出 napi_value* argv, // ⭐ 参数数组输出 napi_value* this_arg, void** data) 有时候函数是对象的方法就需要使用 napi_value* this_arg而 void** data 是自定义附加数据 我们的 napi_get_cb_info(env, info, argc, args, nullptr, nullptr); argc 取地址args是数组名就是地址 */ // ② 先获取字符串长度 size_t src_len; napi_get_value_string_utf8(env, args[0], nullptr, 0, src_len); /* 官方原型 NAPI_EXTERN napi_status napi_get_value_string_utf8(napi_env env, napi_value value,// 要读取的字符串 char* buf, // 字符串拷贝哪里 size_t bufsize, // 缓冲区大小buf的大小 size_t* result) // 输出参数字符串长度 我们的 napi_get_value_string_utf8(env, args[0], nullptr, 0, src_len); args[0],要读取的字符串 nullptr,不复制数据 0,缓冲区大小0 src_len,把长度写进 src_len */ // ③ 开辟内存 char *src_buf new char[src_len 1]; napi_get_value_string_utf8(env, args[0], src_buf, src_len 1, src_len); /* napi_get_value_string_utf8(env, args[0], src_buf, src_len 1, src_len); 这一次和第一次不同 args[0]要读取的字符串 src_buf 已经开好的内存字符串会被复制到这里 src_len 1缓冲区大小 src_len实际拷贝的字节长度 */ // ④ 计算压缩需要多大空间 uLong dest_len compressBound(src_len); Bytef *dest_buf new Bytef[dest_len]; /* uLong dest_len compressBound(src_len); 官方原版 uLong ZEXPORT compressBound(uLong sourceLen) 传入要压缩的字符串的长度然后计算出安全上限值 Bytef* dest_buf new Bytef[dest_len]; 申请一块足够大的内存用来存放 zlib 压缩后的数据 */ // ⑤ 调用 zlib 官方压缩 compress(dest_buf, dest_len, (const Bytef *)src_buf, src_len); /* 官方原版 compress(dest_buf,dest_len,(const Bytef*)src_buf,src_len); int ZEXPORT compress(Bytef *dest, // 输出压缩后的数据 uLongf *destLen, // 输入\输出 缓冲区大小 / 真石压缩长度 const Bytef *source,// 要压缩的原始数据 uLong sourceLen) // 原始数据长度 我们的 compress(dest_buf,dest_len,(const Bytef*)src_buf,src_len); (const Bytef*)src_buf因为 zlib 要的是 Bytef* 类型我们强制转换 */ // ⑥ 压缩结果包含二进制数据不能直接当 UTF-8 字符串传递 // 必须先 base64 编码保证数据安全通过字符串边界 std::string b64 base64_encode(dest_buf, dest_len); napi_value result; napi_create_string_utf8(env, b64.c_str(), b64.size(), result); // ⑦ 释放内存 delete[] src_buf; delete[] dest_buf; return result; } // 封装解压函数 static napi_value zlibUncompress(napi_env env, napi_callback_info info) { // ① 获取 ArkTS 传过来的参数 size_t argc 1; napi_value args[1] {nullptr}; napi_get_cb_info(env, info, argc, args, nullptr, nullptr); // ② 先获取字符串长度 size_t src_len; napi_get_value_string_utf8(env, args[0], nullptr, 0, src_len); // ③ 开辟内存读入 base64 字符串 char *b64_buf new char[src_len 1]; napi_get_value_string_utf8(env, args[0], b64_buf, src_len 1, src_len); // ④ base64 解码为原始压缩二进制数据 std::vectorunsigned char raw base64_decode(std::string(b64_buf, src_len)); delete[] b64_buf; // ⑤ 计算解压需要的最大空间 uLong dest_len raw.size() * 8; Bytef *dest_buf new Bytef[dest_len]; /* 解压时无法像压缩一样用 compressBound 预知大小 所以使用官方推荐策略先分配足够大的缓冲区 */ // ⑥ 调用 zlib 官方解压 int ret uncompress(dest_buf, dest_len, raw.data(), raw.size()); /* 官方原型 int ZEXPORT uncompress(Bytef *dest, // 输出解压后的数据 uLongf *destLen, // 输入缓冲区大小 / 输出真实解压长度 const Bytef *source, // 压缩数据 uLong sourceLen); // 压缩数据长度 ret 是解压的返回值表示成功与否 */ // ⑦ 解压失败判断 if (ret ! Z_OK) { delete[] dest_buf; napi_throw_error(env, nullptr, zlib uncompress failed); return nullptr; } /* 官方原型 NAPI_EXTERN napi_status napi_throw_error(napi_env env, const char* code,// 错误码 const char* msg) // 错误信息 */ // ⑦ 把解压结果转回给 ArkTS napi_value result; napi_create_string_utf8(env, (const char *)dest_buf, dest_len, result); // ⑧ 释放内存 delete[] dest_buf; return result; } EXTERN_C_START static napi_value Init(napi_env env, napi_value exports) { napi_property_descriptor desc[] { {add, nullptr, Add, nullptr, nullptr, nullptr, napi_default, nullptr}, {compress, nullptr, zlibCompress, nullptr, nullptr, nullptr, napi_default, nullptr}, {uncompress, nullptr, zlibUncompress, nullptr, nullptr, nullptr, napi_default, nullptr}}; napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); return exports; } EXTERN_C_END static napi_module demoModule { .nm_version 1, .nm_flags 0, .nm_filename nullptr, .nm_register_func Init, .nm_modname library, .nm_priv ((void *)0), .reserved {0}, }; extern C __attribute__((constructor)) void RegisterLibraryModule(void) { napi_module_register(demoModule); }有一个地方需要解释为什么使用Base64因为zlib 压缩输出是原始二进制字节流绝对不能直接用 UTF-8 字符串回传。二进制中包含\0结束符与非法 UTF-8 字节直接转字符串会导致数据截断、损坏最终解压失败4.修改Index.d.ts代码示例export const add: (a: number, b: number) number; export const compress: (str: string) string; export const uncompress: (str: string) string;5.生成library.har并放到Entry 模块中找到har后复制粘贴到har目录要自建在Entry模块中的Index.ets中引入使用并验证import { hilog } from kit.PerformanceAnalysisKit; import testNapi from libentry.so; import * as myLib from library; const DOMAIN 0x0000; Entry Component struct Index { State originStr: string Hello 鸿蒙 ZLIB 测试; State compressStr: string ; State uncompressStr: string ; State resultTip: string ; build() { Row() { Column() { Text(原始 this.originStr) .fontSize(20) .margin(10); Button(开始压缩 解压) .onClick(() { try { // 1. 压缩 this.compressStr myLib.compress(this.originStr); // 2. 解压 this.uncompressStr myLib.uncompress(this.compressStr); // 3. 判断是否一致 if (this.uncompressStr this.originStr) { this.resultTip ✅ 验证成功解压后和原文一样; } else { this.resultTip ❌ 验证失败内容不一致; } hilog.info(0, ZLIB, 测试完成); } catch (e) { this.resultTip ❌ 报错 JSON.stringify(e); } }) .margin(10); // 压缩结果 Text(压缩后 this.compressStr) .fontSize(16) .margin(10) .opacity(0.6); // 解压结果 Text(解压后 this.uncompressStr) .fontSize(20) .margin(10); // 最终结论 Text(this.resultTip) .fontSize(22) .fontColor(this.resultTip.includes(✅) ? Color.Green : Color.Red) .margin(15); // 直接显示结果在页面上 Text(2 3 testNapi.add(2,3)) .fontSize(30) Text(2 * 3 testNapi.mul(2,3)) .fontSize(30) } .width(100%) } .height(100%) } }我当时遇到的很多细节上的问题很多想不起来了、无法罗列如果你也遇到了欢迎留言。展示