鸿蒙PC C/C++三方库交叉编译实战:以 OpenSSL 为例(lycium++ / HPKBUILD 全流程)
一、为什么要再写一篇"三方库交叉编译"
在鸿蒙应用开发中,很多场景必须调用 C/C++ 层能力:
- 音视频处理:FFmpeg、mpv、libplacebo
- 图像处理:OpenCV、libjpeg、libpng
- 加密安全:OpenSSL、libsodium ← 本文主角
- 算法 / 游戏引擎:各种已有的 C/C++ 实现
实验手册里演示了 cups、libplacebo 的编译,但 OpenSSL 是加密安全领域最常被搜到的痛点库:
1. 它不用 CMake,而是用自家的 Configure 脚本,交叉编译写法完全不同;
2. 鸿蒙 PC 用的是 musl libc,而 OpenSSL 默认假设 glibc,最容易在链接期翻车;
3. 几乎所有需要 HTTPS / 签名 / 摘要的鸿蒙应用都用得上。
所以拿 OpenSSL 当"另一个库"来写,比再写一个图像库更有价值。
---
二、环境准备(一句话带过,不重复造轮子)
实验手册的前半部分已经讲得很细,这里只给结论,照做即可:
|
项目 |
版本 / 说明 |
|
操作系统 |
Ubuntu 22.04 LTS(虚拟机 / 云主机均可) |
|
OHOS SDK |
6.0 Release 及以上(解压后 native 工具链 + toolchains) |
|
lycium++ |
git clone https://atomgit.com/OpenHarmonyPCDeveloper/lycium_plusplus.git |
|
鸿蒙 PC |
6.0 Release 及以上(真机验证用) |
配置两个关键环境变量(路径改成你自己的实际解压目录):
# ~/.bashrc 末尾追加
export OHOS_SDK=/home/ohpkg/linux # 指向包含 native 的那一层
export HNP_TOOL=/home/ohpkg/linux/toolchains/hnpcli
source ~/.bashrc
# 验证工具链存在
ls $OHOS_SDK/native/llvm/bin/aarch64-linux-ohos-clang
基础编译工具也别漏:
sudo apt-get install gcc g++ cmake make ninja-build pkg-config autoconf automake git git-lfs
新手提示:如果出现 ninja: command not found,sudo apt install ninja-build 即可;arm64-v8a 构建失败且报 C 预处理器被错配成 clang++,去 lycium/script/envset.sh 的 setarm64ENV() 把 export CPP=${CXX} 改成正确的 C 预处理配置再构建。
---
三、OpenSSL 的适配难点与整体思路
OpenSSL 的交叉编译有三个特殊点:
1. 构建系统特殊:不是 cmake/autotools,而是 ./Configure [target]。目标平台用 linux-aarch64(64 位)/ linux-armv4(32 位)。
2. 交叉编译器前缀:通过 --cross-compile-prefix=aarch64-linux-ohos- 指定。OHOS SDK 提供了 aarch64-linux-ohos-clang 等 clang 包装器,自带 musl sysroot,不需要我们手动配 --sysroot。
3. musl 兼容:加上 no-tests 关掉自测(自测依赖宿主 glibc 工具链),shared 产出 .so 方便 HNP 分发。
整体流程仍是 lycium++ 的标准套路:./build.sh openssl → 框架解析 HPKBUILD → 对每个架构执行 prepare() → build() → package() → archive()。
---
四、编写 HPKBUILD(核心)
进入 thirdparty 目录,建立标准适配仓,目录名必须和 `pkgname` 一致:
cd /home/lycium_plusplus/thirdparty
mkdir -p openssl && cd openssl
touch HPKBUILD
写入以下内容(已对齐 lycium++ 最新字段规范):
# Contributor: your-name <you@example.com>
# Maintainer: your-name <you@example.com>
pkgname=openssl
pkgver=3.2.1
pkgrel=0
pkgdesc="OpenSSL is a robust, full-featured Open Source Toolkit for TLS and SSL"
url="https://www.openssl.org/"
archs=("arm64-v8a") # 主流只做 64 位即可;需要 32 位再加 "armeabi-v7a"
license=("Apache-2.0")
depends=()
makedepends=()
source="https://www.openssl.org/source/openssl-${pkgver}.tar.gz"
downloadpackage=true
autounpack=true
buildtools= # 留空:OpenSSL 不是 cmake/autotools,由 build() 自行处理
builddir=openssl-$pkgver
packagename=$builddir.tar.gz
prepare() {
# 1. 创建 out-of-source 构建目录(按架构隔离)
mkdir -p $builddir/$ARCH-build
}
build() {
cd $builddir
if [ $ARCH == "arm64-v8a" ]; then
TARGET="linux-aarch64"
CROSS="aarch64-linux-ohos-"
else
TARGET="linux-armv4"
CROSS="arm-linux-ohos-"
fi
# 2. 调用 OpenSSL 自家的 Configure,传入交叉编译前缀与安装路径
./Configure $TARGET \
--cross-compile-prefix=$CROSS \
--prefix=$LYCIUM_ROOT/usr/$pkgname/$ARCH \
--openssldir=$LYCIUM_ROOT/usr/$pkgname/$ARCH/ssl \
shared no-tests zlib-dynamic
$MAKE -j$(nproc)
ret=$?
cd $OLDPWD
return $ret
}
package() {
cd $builddir
# install_sw 只装库与头文件,跳过文档,速度更快
$MAKE install_sw install_ssldirs
cd $OLDPWD
}
archive() {
# 生成 HNP 标准包 + tar 归档,便于分发
mkdir -p ${LYCIUM_ROOT}/output/$ARCH
pushd $LYCIUM_ROOT/usr/$pkgname/$ARCH
tar -zvcf ${LYCIUM_ROOT}/output/$ARCH/${pkgname}_${pkgver}.tar.gz *
popd
cp hnp.json $LYCIUM_ROOT/usr/$pkgname/$ARCH
${HNP_TOOL} pack -i $LYCIUM_ROOT/usr/$pkgname/$ARCH -o ${LYCIUM_ROOT}/output/$ARCH/
}
cleanbuild() {
rm -rf $builddir/$ARCH-build
}
要点说明: - archs 决定构建几遍;依赖库的 archs 必须是当前库的超集。 - buildtools 留空时,框架只注入编译器环境变量,不自动拼 cmake/configure 参数,正好适配 OpenSSL 的 Configure。 - archive() 可选;不写也能编译出产物,只是少了 .hnp 包。
---
五、一键构建 & HNP 打包
回到 lycium 目录执行:
cd /home/lycium_plusplus/lycium/
./build.sh openssl
成功标志是日志出现类似 Build openssl 3.2.1 end! ALL JOBS DONE!!!。
产物位置:
lycium/usr/openssl/arm64-v8a/
├── lib/ # libssl.so libcrypto.so
├── include/openssl # 头文件
└── ssl/ # 默认配置
lycium/output/arm64-v8a/
├── openssl_3.2.1.tar.gz
└── openssl.hnp # HNP 安装包
如果提示 pack: command not found,说明 HNP_TOOL 没配好,回去检查 toolchains 是否解压、环境变量是否 source。
---
六、ABI 校验(关键,别跳过)
编译完别急着用,先用 `llvm-readelf` 确认 ABI 真的对:
$OHOS_SDK/native/llvm/bin/llvm-readelf -h \
$LYCIUM_ROOT/usr/openssl/arm64-v8a/lib/libssl.so | grep -E "Machine|Class"
期望输出:
Class: ELF64
Machine: AArch64
- Machine: AArch64 → 架构正确;
- 若误链到 x86 宿主库,Machine 会是 X86-64,真机上必定 dlopen 失败。
同时确认它是动态链接到 musl 而非 glibc:
$OHOS_SDK/native/llvm/bin/llvm-readelf -d libssl.so | grep NEEDED
应看到依赖 libc.so(musl) 而不是 libc.so.6(glibc)。这一步能提前挡掉 90% 的"真机跑不起来"。
---
七、DevEco Studio 集成 & ArkTS 调用
拿到 .so + 头文件后,在 DevEco Studio 的 Native C++ 工程里接入。核心链路是:
ArkTS/ETS → NAPI 桥接层(C++) → OpenSSL C API → 结果原路返回 UI
1)CMakeLists.txt 链接 OpenSSL
把 lib/ 和 include/ 拷到工程 entry/libs/arm64-v8a/ 与 entry/src/main/cpp/include/,然后:
target_include_directories(entry PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_directories(entry PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/arm64-v8a)
target_link_libraries(entry PUBLIC libssl.so libcrypto.so hilog_ndk.z)
2)NAPI 桥接(napi_init.cpp)
下面封装一个 sha256(text) 给 ArkTS 用:
#include "napi/native_api.h"
#include <openssl/evp.h>
#include <string>
#include <cstdio>
static napi_value Sha256(napi_env env, napi_callback_info info) {
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
size_t len = 0;
napi_get_value_string_utf8(env, args[0], nullptr, 0, &len);
std::string input(len, '\0');
napi_get_value_string_utf8(env, args[0], &input[0], len + 1, &len);
unsigned char hash[EVP_MAX_MD_SIZE];
unsigned int hashLen = 0;
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
EVP_DigestInit_ex(ctx, EVP_sha256(), nullptr);
EVP_DigestUpdate(ctx, input.c_str(), input.size());
EVP_DigestFinal_ex(ctx, hash, &hashLen);
EVP_MD_CTX_free(ctx);
char hex[EVP_MAX_MD_SIZE * 2 + 1] = {0};
for (unsigned int i = 0; i < hashLen; i++) {
sprintf(hex + i * 2, "%02x", hash[i]);
}
napi_value result;
napi_create_string_utf8(env, hex, strlen(hex), &result);
return result;
}
EXTERN_C_START
static napi_value Init(napi_env env, napi_value exports) {
napi_property_descriptor desc[] = {
{"sha256", nullptr, Sha256, nullptr, nullptr, nullptr, napi_default, nullptr}
};
napi_define_properties(env, exports, 1, 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 = "entry",
.nm_priv = nullptr,
.nm_int = nullptr,
};
extern "C" __attribute__((constructor)) void RegisterEntryModule(void) {
napi_module_register(&demoModule);
}
3)ArkTS 调用(Index.ets)
import openssl from 'libentry.so'
@Entry
@Component
struct Index {
@State message: string = '点击计算 SHA256'
build() {
Column() {
Text(this.message).fontSize(18).margin(20)
Button('计算 "Hello HarmonyOS" 的 SHA256')
.onClick(() => {
this.message = openssl.sha256('Hello HarmonyOS')
})
}
.width('100%')
.height('100%')
.justifyContent(FlexAlign.Center)
}
}
真机验证:鸿蒙 MateBook Pro 开启开发者模式 + USB 调试,DevEco 选真机 → Shift+F10 运行,按钮点击后返回正确的 64 位十六进制摘要即大功告成。
---
八、踩坑记录(省你两小时)
|
现象 |
原因 |
解决 |
|
./Configure: No such file |
源码未 clone / 解压 |
确认 downloadpackage=true 或手动 git clone 到 builddir |
|
链接报 undefined reference 且指向 getcontext/setcontext |
OpenSSL 某些汇编用到 glibc 特性 |
加 no-async 关掉异步引擎 |
|
真机 dlopen failed: cannot locate symbol |
误用了宿主 x86 库 |
回到第六步用 llvm-readelf 核对 Machine/Class |
|
pack: command not found |
HNP_TOOL 未配置 |
解压 toolchains,export HNP_TOOL=.../hnpcli 并 source |
|
arm64-v8a 失败但 armeabi-v7a 成功 |
envset.sh 的 setarm64ENV() 把 CPP 设错 |
修正为正确 C 预处理器后重新构建 |
---
九、总结
通过 lycium++ 的 HPKBUILD,我们把实验里的 cups/libplacebo 换成了更实用的 OpenSSL,完整跑通了:
1. Ubuntu + OHOS SDK + lycium++ 环境;
2. 针对 Configure 型库编写 HPKBUILD;
3. ./build.sh openssl 一键产出 .so / .a 与 openssl.hnp;
4. llvm-readelf 做 ABI / musl 校验,提前规避真机报错;
5. DevEco Studio + NAPI 打通 ArkTS → C++ → OpenSSL 调用链。
标准化移植的精髓就一句话:让框架管交叉编译环境和多架构循环,你只专心写 `HPKBUILD` 的 prepare/build/package。后续想接 zlib、curl(curl 还依赖 openssl + zlib,框架会自动按依赖顺序构建)也都是同样的套路。
---
如果这篇对你有帮助,麻烦 点赞 + 收藏 + 转发 三连支持~ 后续还会带来更多鸿蒙 PC 三方库移植实战(FFmpeg、OpenCV 等)。欢迎在评论区交流你编译时踩到的坑,一起共建鸿蒙化 C/C++ 三方库生态。
更多推荐



所有评论(0)