【鸿蒙PC】AtomCode驱动NAPI完成鸿蒙化三方库libsodium集成
欢迎加入【开源鸿蒙PC社区】,一起共建鸿蒙化C/C++三方库生态。
仓库: jedisct1/libsodium v1.0.22 — A modern, portable, easy to use crypto library
集成平台: HarmonyOS NEXT / OpenHarmony API 20+
集成方式: NAPI (Native API) + ArkTS
| 资源 | 地址 |
|---|---|
| libsodium 上游仓库 | https://github.com/jedisct1/libsodium |
| libsodium 鸿蒙化适配后仓库 | https://atomgit.com/unisources/libsodium |
| lycium_plusplus 框架 | https://atomgit.com/OpenHarmonyPCDeveloper/lycium_plusplus |
| 示例工程 | https://atomgit.com/unisources/OHOSLibsodiumSample |

一、前言
不知道你有没有这种经历:交叉编译通过了,libsodium 的 806KB 静态库也部署到项目里了,结果写 NAPI 桥接时发现需要为 50 多个头文件的加密库写上百个函数封装——从 crypto_secretbox 到 crypto_sign,每一个都要手动处理 napi_typeof、napi_create_string_utf8……
libsodium 是一个现代、可移植、易用的加密库,提供对称加密、公钥加密、数字签名、密码哈希等全方位加密能力,API 数量超过 200 个。将这样一个大型加密库集成到 HarmonyOS 应用中,涉及 CMake 配置、NAPI 桥接、TypeScript 类型声明、ArkUI 页面开发 四个环节。而 libsodium 的一个特殊挑战是:它的头文件是 configure 阶段动态生成的(如 sodium/version.h),部署时容易遗漏。
本文以 libsodium 为例,完整展示如何使用 AtomCode + Skills 将已鸿蒙化的 C/C++ 加密库集成到 HarmonyOS NEXT 应用中,覆盖 13 个 NAPI 导出函数,涵盖 libsodium 全部主要功能。
二、传统集成的效率瓶颈
在 HarmonyOS 应用中集成一个 C/C++ 三方库,传统流程如下:
| 阶段 | 主要痛点 |
|---|---|
| 工程搭建 | 手动创建目录结构、修改 config 文件 |
| 库文件部署 | 拷贝头文件和 .a 到正确位置,容易遗漏生成的 version.h |
| CMake 配置 | include_directories 废弃后需改用 target_include_directories |
| NAPI 桥接 | 为 200+ API 编写重复的 napi_wrapper 模板代码 |
| 类型声明 | 13 个函数签名必须与 C++ 精确匹配 |
| UI 验证 | 13+ 个测试按钮和结果展示区域 |
| 编译排错 | 跨语言调试、头文件缺失、生成文件遗漏 |
关键点:最棘手的环节是 NAPI 桥接代码编写和 编译错误排错。libsodium 的特殊挑战在于
version.h是configure阶段动态生成的,从构建产物中复制到项目时容易被忽略。
三、AtomCode + Skills 解决方案
本次集成全流程使用了以下 Skills:
| Skill | 阶段 | 作用 |
|---|---|---|
lycium-app-integration |
集成 | 核心:指导 NAPI 桥接、CMake 链接、ArkUI 集成 |
skills:harmonyos-app-integration |
集成 | 补充鸿蒙应用集成指引(项目配置、设备适配) |
lycium-build-check |
验证 | 检查交叉编译产物架构 |
skills:harmonyos-napi-samples |
参考 | 查看 NAPI 集成参考示例 |
工作流程概览
① 工程创建 ──→ ② 三方库部署 ──→ ③ CMake 配置
│
⑥ 编译修复 ←── ⑤ 编译验证 ←──┘
│
④ NAPI + TS + ArkUI 并行生成
四、全流程实操
4.1 工程创建 —— DevEco Studio 模板
使用 DevEco Studio 创建 Native C++ 工程:
| 配置项 | 值 | 说明 |
|---|---|---|
| 设备类型 | 2in1 |
必须勾选目标设备以生成正确 ABI 配置 |
| SDK 版本 | API 20+ | 确保支持 NAPI 的完整能力 |
| 模板 | Native C++ | 预置 CMake 和 NAPI 入口文件 |
4.2 三方库部署
部署 libsodium 交叉编译产物时,需要特别注意 动态生成的文件:
| 步骤 | 手动操作 | AtomCode 自动操作 |
|---|---|---|
| 静态库 | 拷贝 libsodium.a(806KB) |
自动部署 |
| 源码头文件 | 拷贝 sodium/*.h(86 个) |
自动部署 |
| 生成版本头 | 从构建产物拷贝 sodium/version.h |
自动识别并部署 |
| 顶层头文件 | 拷贝 sodium.h(#include 入口) |
自动部署 |
关键点:libsodium 的
version.h是在configure阶段动态生成的,不在源码 tarball 中。它在arm64-v8a-build/src/libsodium/include/sodium/version.h路径下。同时,顶层sodium.h(src/libsodium/include/sodium.h)也不在include/目录中——它是 libsodium 的主要公共入口,必须从源码目录额外复制。
部署后的头文件结构:
thirdparty/libsodium/include/
├── sodium.h # 顶层入口(从 src/libsodium/include/ 复制)
├── sodium/
│ ├── version.h # 生成文件(从构建产物复制)
│ ├── core.h, export.h, ...
│ ├── crypto_secretbox.h # 对称加密
│ ├── crypto_box.h # 公钥加密
│ ├── crypto_sign.h # 数字签名
│ ├── crypto_pwhash.h # 密码哈希
│ ├── crypto_auth.h # 认证
│ └── ... # 共 88 个文件
4.3 CMake 配置 —— 自动适配
# ── AI 自动添加 ──
cmake_minimum_required(VERSION 3.5.0)
project(libsodium)
set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR})
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
add_library(entry SHARED napi_init.cpp)
# 头文件路径:注意包含 sodium/ 子目录(version.h 在其中)
target_include_directories(entry PRIVATE
${NATIVERENDER_ROOT_PATH}
${NATIVERENDER_ROOT_PATH}/thirdparty/libsodium/include
${NATIVERENDER_ROOT_PATH}/thirdparty/libsodium/include/sodium)
# 链接 libsodium 静态库
target_link_libraries(entry PUBLIC libace_napi.z.so)
target_link_libraries(entry PUBLIC
${NATIVERENDER_ROOT_PATH}/thirdparty/libsodium/lib/libsodium.a)
# ── 自动添加结束 ──
关键点:
include/sodium/子目录必须加入target_include_directories,因为sodium.h内部通过#include "sodium/version.h"引用子目录头文件。同时sodium.h自身在include/根目录,需要include/也在搜索路径中。
4.4 NAPI 桥接 —— 13 个导出函数
最核心的环节。NAPI 桥接需要解决三个关键问题:头文件依赖、二进制安全、类型安全。对于 libsodium,NAPI 桥接直接调用 C API 并返回字符串结果,无需实例管理。
13 个 NAPI 函数分类
| 分类 | 函数数 | 对应 libsodium API | ArkTS 调用示例 |
|---|---|---|---|
| 基础 | 2 | sodium_version_string, sodium_init |
testNapi.libsodiumVersion() |
| 对称加密 | 2 | crypto_secretbox_easy, crypto_stream_xor |
testNapi.libsodiumSecretBox() |
| 公钥加密 | 1 | crypto_box_easy |
testNapi.libsodiumBox() |
| 密钥交换 | 1 | crypto_kx |
testNapi.libsodiumKeyx() |
| 数字签名 | 1 | crypto_sign |
testNapi.libsodiumSign() |
| 哈希 | 1 | crypto_generichash + crypto_hash_sha256 |
testNapi.libsodiumHash() |
| 密码哈希 | 1 | crypto_pwhash (Argon2id) |
testNapi.libsodiumPwHash() |
| 认证 | 1 | crypto_auth (HMAC) |
testNapi.libsodiumAuth() |
| 秘密流 | 1 | SecretStream push+pull |
testNapi.libsodiumSecretStream() |
| 随机数 | 1 | randombytes_buf (ArrayBuffer) |
testNapi.libsodiumRandom() |
| 常量 | 1 | 各算法参数 | testNapi.libsodiumConstants() |
代码深度解读
模式 1:MkBuf 工具函数 —— ArrayBuffer 支持
// 创建 ArrayBuffer 返回二进制数据(如随机数)
static napi_value MkBuf(napi_env env, const void *data, size_t len) {
napi_value result;
void *buf = nullptr;
napi_create_arraybuffer(env, len, &buf, &result);
if (data && len > 0) memcpy(buf, data, len);
return result;
}
// 使用示例:libsodiumRandom 返回 32 字节 ArrayBuffer
static napi_value LibsodiumRandom(napi_env env, napi_callback_info info) {
(void)info;
unsigned char buf[32];
randombytes_buf(buf, sizeof(buf));
return MkBuf(env, buf, sizeof(buf)); // ← 返回 ArrayBuffer 而非 string
}
设计解读:
libsodiumRandom返回ArrayBuffer而非string,确保二进制随机数不被\0截断。ArkTS 侧通过.byteLength获取长度。
模式 2:初始化检查
static napi_value LibsodiumInit(napi_env env, napi_callback_info info) {
(void)info;
int ret = sodium_init(); // 可多次调用,只有第一次实际执行
return MkStr(env, ret >= 0 ? "✅ sodium_init OK" : "❌ sodium_init failed");
}
设计解读:libsodium 的
sodium_init()是线程安全的,可多次调用——只有第一次调用实际执行初始化,后续调用直接返回成功。NAPI 函数直接暴露这一行为。
模式 3:加解密回环测试
static napi_value LibsodiumSecretBox(napi_env env, napi_callback_info info) {
(void)info;
std::ostringstream log;
unsigned char key[crypto_secretbox_KEYBYTES];
unsigned char nonce[crypto_secretbox_NONCEBYTES];
crypto_secretbox_keygen(key);
randombytes_buf(nonce, sizeof(nonce));
const char *plaintext = "Hello libsodium!";
size_t mlen = strlen(plaintext) + 1;
std::vector<unsigned char> ct(mlen + crypto_secretbox_MACBYTES);
crypto_secretbox_easy(ct.data(), (const unsigned char*)plaintext, mlen, nonce, key);
std::vector<unsigned char> dec(mlen);
if (crypto_secretbox_open_easy(dec.data(), ct.data(), ct.size(), nonce, key) == 0) {
log << "✅ SecretBox encrypt/decrypt OK\n";
log << " \"" << (char*)dec.data() << "\"\n";
} else {
log << "❌ SecretBox decrypt failed\n";
}
return MkStr(env, log.str());
}
设计解读:
crypto_secretbox_easy是 libsodium 最常用的对称加密接口——一个函数完成加密+认证。crypto_secretbox_open_easy解密并验证完整性。这是libsodium 优于 OpenSSL的典型例子:OpenSSL 需要十几步配置,libsodium 只需要一个函数调用。
模式 4:密钥交换与公钥加密链
// 密钥交换:生成双方密钥对,建立会话密钥
static napi_value LibsodiumKeyx(napi_env env, napi_callback_info info) {
unsigned char alice_sk[crypto_kx_SECRETKEYBYTES];
unsigned char alice_pk[crypto_kx_PUBLICKEYBYTES];
unsigned char bob_sk[crypto_kx_SECRETKEYBYTES];
unsigned char bob_pk[crypto_kx_PUBLICKEYBYTES];
crypto_kx_keypair(alice_pk, alice_sk);
crypto_kx_keypair(bob_pk, bob_sk);
unsigned char alice_rx[crypto_kx_SESSIONKEYBYTES];
unsigned char alice_tx[crypto_kx_SESSIONKEYBYTES];
crypto_kx_client_session_keys(alice_rx, alice_tx, alice_pk, alice_sk, bob_pk);
// Alice TX == Bob RX,建立安全会话
}
关键点:libsodium 的密钥交换(
crypto_kx)和公钥加密(crypto_box)在逻辑上构成完整链——crypto_kx建立共享密钥,crypto_box用共享密钥加密消息。两个 NAPI 函数组合使用可模拟端到端加密通信。
4.5 类型声明和 UI 页面并行生成
AtomCode 的 parallel_edit_files 能力可以同时修改多个无关文件:
Index.d.ts(13 个类型声明)
export const libsodiumVersion: () => string;
export const libsodiumInit: () => string;
export const libsodiumRandom: () => ArrayBuffer; // 二进制数据
export const libsodiumSecretBox: () => string;
export const libsodiumHash: () => string;
export const libsodiumConstants: () => string;
export const libsodiumKeyx: () => string;
export const libsodiumBox: () => string;
export const libsodiumSign: () => string;
export const libsodiumPwHash: () => string;
export const libsodiumStream: () => string;
export const libsodiumAuth: () => string;
export const libsodiumSecretStream: () => string;
设计解读:
libsodiumRandom返回ArrayBuffer(二进制安全),其余返回string(文本报告)。所有 13 个函数无参数——每个函数自包含地测试一种加密能力。
Index.ets(ArkUI 页面,双列 Grid 布局)
@Entry
@Component
struct Index {
build() {
Column() {
// 顶栏
Row() {
Text('libsodium')
Text('13 functions')
}
Scroll() {
Grid() {
// 13 个卡片,每个对应一个 NAPI 函数
GridItem() { FuncCard({ title: '📋 版本', sub: 'sodium_version_string',
action: () => `✅ ${testNapi.libsodiumVersion()}` }) }
GridItem() { FuncCard({ title: '⚡ 初始化', sub: 'sodium_init',
action: () => testNapi.libsodiumInit() }) }
// ... 共 13 个卡片
}
.columnsTemplate('1fr 1fr') // 双列布局
}
}
}
}
// 每个卡片独立响应式组件
@Component
struct FuncCard {
@State result: string = ''; // 每个卡片自己的状态
private action: () => string = () => '';
build() {
Column() {
// 卡片头部
Row() { /* title + arrow */ }
// 卡片结果(点击后显示)
if (this.result) {
Text(this.result).fontFamily('Courier New')
}
}
.onClick(() => { this.result = this.action(); })
}
}
关键点:使用
@Component+@State替代@Builder,确保每个卡片点击后独立响应@State result更新。Grid.columnsTemplate('1fr 1fr')实现双列布局,每行 2 个卡片。
4.6 编译错误自动修复 —— 闭环诊断
集成过程中,AtomCode 自动发现并修复了以下典型错误:
修复问题 1:sodium.h 未找到
现象:
fatal error: 'sodium.h' file not found
#include "sodium.h"
根因:libsodium 的公共头文件 sodium.h 位于 src/libsodium/include/sodium.h,不在标准的 include/ 目录中。部署时只复制了 include/sodium/ 子目录,遗漏了顶层 sodium.h。
修复方案:
+ cp src/libsodium/include/sodium.h thirdparty/libsodium/include/
修复问题 2:sodium/version.h 未找到
现象:
fatal error: 'sodium/version.h' file not found
#include "sodium/version.h"
根因:version.h 是 configure 阶段动态生成的文件,不在源码 tarball 中。它在 arm64-v8a-build/src/libsodium/include/sodium/version.h 中,需要从构建产物目录复制。
修复方案:
+ cp arm64-v8a-build/src/libsodium/include/sodium/version.h \
+ thirdparty/libsodium/include/sodium/
| 错误类型 | AI 自动修复 |
|---|---|
| 头文件缺失(sodium.h) | < 10 s |
| 生成文件缺失(version.h) | < 30 s |
| CMake 路径错误 | < 10 s |
@Builder 非响应式状态 |
< 2 min(改用 @Component) |
五、效率对比总结
| 阶段 | AI 辅助耗时 |
|---|---|
| 工程搭建 | 5 min |
| 库文件部署(88 个头文件) | 30 s |
| CMake 配置 | 10 s |
| NAPI 桥接(13 个函数) | 15 s |
| 类型声明 + UI(双列 Grid) | 10 s |
| 编译排错(头文件 + 生成文件) | 2 min |
| 合计 | ~8-10 min |
关键点:AI 自动处理了大部分模板代码和排错环节。libsodium 的
version.h生成文件和sodium.h顶层头文件的部署是最大陷阱——AI 自动从构建产物定位并复制。
六、最佳实践建议
6.1 集成前准备
- 确认头文件完整性:
#include "sodium.h"能正确找到 - 检查生成文件:
sodium/version.h必须存在(从构建产物复制) - 验证符号完整性:
$ nm thirdparty/libsodium/lib/libsodium.a | grep " T " | wc -l 866 # ✅ libsodium 导出符号验证
6.2 集成中注意
- Autotools 生成文件:libsodium 的
version.h是configure阶段生成的,从arm64-v8a-build/src/libsodium/include/sodium/version.h复制 - 顶层头文件:
sodium.h在src/libsodium/include/sodium.h,不是标准 include 路径 - ArkTS 响应式状态:用
@Component+@State而非@Builder参数传递,确保点击后重渲染 - 双列布局:
Grid.columnsTemplate('1fr 1fr')比手动Row包裹更简洁
6.3 集成后验证
- 编译验证:
./hvigorw assemble --mode debug - 功能测试:点击 13 个卡片逐项验证加密功能
- 随机数验证:多次点击
libsodiumRandom确认每次返回不同数据
七、总结
libsodium 的 NAPI 集成是一个从零到一的完整案例,覆盖了鸿蒙应用集成 C/C++ 加密库的 6 个核心环节。最终产出 13 个 NAPI 导出函数,涵盖 libsodium 全部主要功能:对称加密、公钥加密、数字签名、密钥交换、密码哈希、流加密、认证、秘密流。
本项目的一个关键经验是:Autotools 构建的库通常会有 configure 阶段动态生成的文件(如 libsodium 的 version.h),这些文件不在源码 tarball 中,需要从构建产物目录手动复制。AtomCode 自动检测并完成了这一步骤。
下期预告:下一期我们将集成 OpenSSL(鸿蒙 PC 上最复杂的加密库集成挑战),届时将展示如何处理多文件部署、汇编优化、Perl 生成器等复杂场景。
附录:OHOSLibsodiumSample 项目结构
OHOSLibsodiumSample/
├── AppScope/app.json5 # 应用配置(bundleName: com.unisources.libsodium)
├── entry/src/main/
│ ├── cpp/
│ │ ├── CMakeLists.txt # C++ 构建,链接 libsodium.a
│ │ ├── napi_init.cpp # 311 行,13 个 NAPI 导出函数
│ │ ├── thirdparty/libsodium/
│ │ │ ├── include/sodium.h # libsodium 顶层入口头文件
│ │ │ ├── include/sodium/version.h # 生成文件(configure 阶段生成)
│ │ │ ├── include/sodium/*.h # 86 个加密 API 头文件
│ │ │ └── lib/libsodium.a # 806KB arm64-v8a 静态库
│ │ └── types/libentry/Index.d.ts # 13 行类型声明
│ ├── ets/pages/
│ │ └── Index.ets # ArkUI 双列 Grid 布局(13 张卡片)
│ └── module.json5
└── build-profile.json5 # 签名与 SDK 配置
更多推荐




所有评论(0)