一、为什么要再写一篇"三方库交叉编译"

在鸿蒙应用开发中,很多场景必须调用 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++ 三方库生态。

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐