基于 lycium 在 OpenHarmony 上交叉编译 komrad36-CRC 完整实践

欢迎加入 开源鸿蒙跨平台社区,与开发者一起共建鸿蒙三方库的开源生态。


一、背景与目标

komrad36/CRC 是 GitHub 上的 Fast CRC32 开源库,提供多种 CRC-32C(多项式 0x82f63b78)实现,上游侧重 x86 性能(SSE、硬件指令等)。本文记录在 OpenHarmony 三方库共建仓库(tpc_c_cplusplus) 中,使用 lycium 交叉编译框架 将其可移植子集接入、编译、打包并在设备上运行测试的完整过程,以及遇到的问题与解决方案。

目标

  • 在 Mac/Linux 宿主机上交叉编译出适用于 OpenHarmony(armeabi-v7a / arm64-v8a)的静态库与测试可执行文件;
  • 仅编译 naive C++ 与 tabular 等可移植实现,不包含 x86 汇编与硬件指令;
  • 测试可在开发板上通过 ./CRC32Test 执行,输出 [PASS] / [FAIL] 及 "All N tests passed."。

二、环境与框架简介

环境搭建 更详细的步骤可参考:OpenHarmony 交叉编译环境配置。下文仅列出与本文直接相关的要点。

2.1 编译环境搭建

  • 宿主机 :建议 Mac(Darwin)或 Linux;需安装 gccg++cmakemakepkg-configwget 等。

  • OHOS SDK :需包含 native/llvmnative/build-tools/cmakenative/build/cmake/ohos.toolchain.cmake 等;环境变量指向 SDK 根目录,例如:

    bash 复制代码
    export OHOS_SDK=/path/to/OpenHarmony/Sdk/20
  • lycium :每个三方库由目录下的 HPKBUILD 定义"下载 → 解压 → prepare → 编译 → 安装";产物落在 lycium/usr/<pkgname>/<ARCH>/

2.2 komrad36-CRC 上游特点

  • 源码 :使用分支归档 https://github.com/komrad36/CRC/archive/refs/heads/master.tar.gz,解压后为 CRC-master
  • 可移植部分naive_methods_cpp.cpp(逐字节)、tabular_methods.cpp(查表 1/2/4/8/16 字节);不编 x86 汇编、_mm_crc32_*、golden_intel/amd 等。
  • 构建 :上游为 Visual Studio 工程;在 lycium 中通过 prepare() 内 heredoc 生成 CRC/CMakeLists.txtCRC/main_test.cppCRC/crc32.h,再使用 CMake 编译静态库与测试可执行文件 CRC32Test

2.3 HPKBUILD / HPKCHECK 在本库中的角色

  • HPKBUILD :定义 pkgname=komrad36-CRC、source、builddir=CRC-master、archs、buildtools=cmake;prepare()cd $builddir 后通过多个 cat > CRC/xxx << 'ENDxxx' 生成 CMake 与测试源码,build() 中 cmake + make,package() 中拷贝 libkomrad36_crc.acrc32.husr,并删除源码压缩包cleanbuild() 删除解压目录与压缩包。
  • HPKCHECK :设备上进入 ${builddir}/CRC/${ARCH}-build 执行 ./CRC32Test,以退出码作为测试结果。

三、接入步骤

3.1 创建三方库目录与文件

Workspace/tpc_c_cplusplus/thirdparty/ 下新建目录 komrad36-CRC,并准备:

文件 作用
HPKBUILD 包名、源码、prepare/build/package/cleanbuild;prepare 中 heredoc 生成 CMake 与测试
HPKCHECK 设备上执行 ./CRC32Test
README.OpenSource 开源协议、上游地址(JSON 多行格式)
README_zh.md 中文说明:简介、产物、编译、测试、CRC 约定(init 0)等
.gitignore 忽略 CRC-master.tar.gzCRC-master/*.loglast_error

3.2 HPKBUILD 配置要点与重要代码

在 2.3 的通用结构基础上,komrad36-CRC 的取值与差异如下。本库无独立 patch 文件 ,所有构建与测试相关文件均在 prepare() 内通过 heredoc 生成。

komrad36-CRC 取值 / 说明
pkgname komrad36-CRC
pkgver master(上游无 tag,用分支)
source https://github.com/komrad36/CRC/archive/refs/heads/master.tar.gz
builddir CRC-master
buildtools cmake
archs armeabi-v7a、arm64-v8a
depends
prepare():heredoc 生成 CMake、测试源码与头文件

上游为 Visual Studio 工程,无现成 CMake。在 prepare()cd $builddir 后,用三个 heredoc<< 'ENDCMAKE' / 'ENDTEST' / 'ENDHEAD')分别写入 CRC/CMakeLists.txtCRC/main_test.cppCRC/crc32.h结束符必须单独占一行、行首无空格,且使用单引号形式避免变量展开。

CMakeLists.txt 关键内容(仅编可移植源文件、静态链接测试、注册 ctest):

bash 复制代码
cat > CRC/CMakeLists.txt << 'ENDCMAKE'
cmake_minimum_required(VERSION 3.12)
project(komrad36_crc CXX)
set(PORTABLE_SOURCES naive_methods_cpp.cpp tabular_methods.cpp)
add_library(komrad36_crc STATIC ${PORTABLE_SOURCES})
target_include_directories(komrad36_crc PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
add_executable(CRC32Test main_test.cpp)
target_link_libraries(CRC32Test PRIVATE komrad36_crc)
# 静态链接,避免设备上动态链接器路径与 SDK 不一致导致 "No such file or directory"
set_target_properties(CRC32Test PROPERTIES LINK_FLAGS "-static")
enable_testing()
add_test(NAME CRC32Test COMMAND CRC32Test)
ENDCMAKE

main_test.cpp 关键内容(表驱动、期望值 0x58e3fa20):

本库 CRC 使用初始值 0 ,故 "123456789" 的期望值为 0x58e3fa20(非常见 0xe3069283)。测试仅调用 option_5 / option_6,避免 option_9/10 对 9 字节越界。

bash 复制代码
cat > CRC/main_test.cpp << 'ENDTEST'
...
struct TestCase {
    const char* name;
    const char* data;
    uint32_t len;
    uint32_t expected_crc;
};
/* 本实现使用初始值 0,标准 0xe3069283 对应 init=0xFFFFFFFF */
static const TestCase kCases[] = {
    { "empty", "", 0, 0u },
    { "123456789", "123456789", 9, 0x58e3fa20u },
};
int main() {
    printf("CRC32Test (init0, expect 123456789=0x58e3fa20)\n");
    ...
    for (...) {
        uint32_t r5 = option_5_naive_cpp(t->data, t->len);
        uint32_t r6 = option_6_tabular_1_byte(t->data, t->len);
        if (r5 == t->expected_crc && r6 == t->expected_crc)
            printf("[PASS] %s\n", t->name);
        else
            printf("[FAIL] %s expected 0x%08x got ...\n", ...);
    }
    ...
}
ENDTEST

crc32.h :声明 option_5_naive_cpp 以及 option_6~option_10_tabular_* 共六个接口(略,见仓库 HPKBUILD)。最后 mkdir -p CRC/$ARCH-buildcd ${OLDPWD}

build()

源码与构建目录在 $builddir/CRC 下,cmake 使用 -BCRC/$ARCH-build -S CRC,make 在 CRC/$ARCH-build 中执行:

bash 复制代码
build() {
    cd $builddir
    PKG_CONFIG_LIBDIR="${pkgconfigpath}" ${OHOS_SDK}/native/build-tools/cmake/bin/cmake "$@" \
        -DOHOS_ARCH=$ARCH -BCRC/$ARCH-build -S CRC -L > $buildlog 2>&1
    ret=$?
    if [ $ret -ne 0 ]; then
        cd $OLDPWD
        return $ret
    fi
    $MAKE VERBOSE=1 -C CRC/$ARCH-build >> $buildlog 2>&1
    ret=$?
    cd $OLDPWD
    return $ret
}
package()

拷贝静态库与头文件到 usr,并删除源码压缩包(便于后续自行打包):

bash 复制代码
package() {
    cd $builddir
    mkdir -p $LYCIUM_ROOT/usr/$pkgname/$ARCH/lib
    mkdir -p $LYCIUM_ROOT/usr/$pkgname/$ARCH/include
    [ -f CRC/$ARCH-build/libkomrad36_crc.a ] && cp -f CRC/$ARCH-build/libkomrad36_crc.a $LYCIUM_ROOT/usr/$pkgname/$ARCH/lib/
    [ -f CRC/crc32.h ] && cp -f CRC/crc32.h $LYCIUM_ROOT/usr/$pkgname/$ARCH/include/
    cd ${OLDPWD}
    rm -f ${PWD}/$packagename
}
cleanbuild()

删除解压目录与压缩包,保证下次编译重新下载或使用新包:

bash 复制代码
cleanbuild() {
    rm -rf ${PWD}/$builddir
    rm -f ${PWD}/$packagename
}
HPKCHECK:设备上执行 CRC32Test

设备上进入 CRC-master/CRC/${ARCH}-build 后直接运行 ./CRC32Test,以退出码作为测试结果;不依赖 ctest,避免宿主机路径问题:

bash 复制代码
source HPKBUILD > /dev/null 2>&1
logfile=${LYCIUM_THIRDPARTY_ROOT}/${pkgname}/${pkgname}_${ARCH}_${OHOS_SDK_VER}_test.log

checkprepare() { return 0; }

openharmonycheck() {
    res=0
    cd ${builddir}/CRC/${ARCH}-build
    echo "start test times: `date`" >> ${logfile} 2>&1
    if [ -f ./CRC32Test ]; then
        ./CRC32Test >> ${logfile} 2>&1
        res=$?
    else
        echo "No CRC32Test executable, skip test" >> ${logfile} 2>&1
        res=1
    fi
    cd $OLDPWD
    echo "end test times: `date`" >> ${logfile} 2>&1
    return $res
}

3.3 测试用例与 CRC 约定

  • 测试在 prepare() 中生成的 main_test.cpp 内,仅使用 option_5_naive_cpp 与 option_6_tabular_1_byte(按字节处理,无对齐与越界问题);option_9/option_10 按 8/16 字节整块读取,对长度 9 会越界导致 SIGSEGV,故不在测试中调用。
  • 本库 CRC 使用初始值 0 ,因此 "123456789" 的正确结果为 0x58e3fa20 ,而非常见文档中"初始值 0xFFFFFFFF"下的 0xe3069283。测试期望值需写 0x58e3fa20。

四、编译流程与命令

  1. 进入 lycium 目录

    bash 复制代码
    cd /path/to/tpc_c_cplusplus/lycium
  2. 执行编译(仅编译 komrad36-CRC):

    bash 复制代码
    ./build.sh komrad36-CRC
  3. 产物位置

    • 静态库与头文件:lycium/usr/komrad36-CRC/armeabi-v7a/lycium/usr/komrad36-CRC/arm64-v8a/(lib、include)。
    • 测试可执行文件:thirdparty/komrad36-CRC/CRC-master/CRC/<ARCH>-build/CRC32Test,需拷贝到设备后执行。
    • 编译成功后源码压缩包 CRC-master.tar.gz 会被自动删除。

五、遇到的问题与解决方案

问题一:HPKBUILD 报 "syntax error near unexpected token {" / "cleanbuild: command not found"

现象 :执行 ./build.sh komrad36-CRC 时出现 HPKBUILD: line 24: syntax error near unexpected token '{'prepare() {,以及 cleanbuild: command not found;路径被截断为 komrad36-CRC

原因

① heredoc 结束符若带前导空格或非单独一行,shell 不认为结束,后续 prepare() { 被当作 heredoc 内容,导致语法错,source 未完成,cleanbuild 未定义。

② 路径中含 komrad36-CRC 时,未加引号的变量在部分 shell 下会把 36 解析为文件描述符重定向 (如 36>),路径被拆成 komrad36-CRC

HPKBUILD 使用 Windows 换行(CRLF) 时,heredoc 结束行实际为 ENDCMAKE\r,与 ENDCMAKE 不匹配,heredoc 不结束,同样导致解析错乱。

解决

① 三个 heredoc 使用结束符 ENDCMAKE / ENDTEST / ENDHEAD单独占一行、行首无空格 ,且使用 << 'DELIM' 形式。

② 在 lycium/build.shlycium/script/build_hpk.sh 中,对所有涉及路径的变量与数组加双引号 (如 source "${PWD}/HPKBUILD"cd "${notdonelist[$i]}"hpkPaths[...]="$job" 等);build.sh 开头增加"非 bash 则用 bash 重新执行"的逻辑,避免 sh 下数组与引号解析差异。

③ 将 HPKBUILD 转为 Unix 换行(LF)
sed 's/\r$//' HPKBUILD > HPKBUILD.tmp && mv HPKBUILD.tmp HPKBUILD


问题二:package() 报 "cp: -r not specified; omitting directory"

现象:编译日志中出现 "cp: -r not specified; omitting directory" 的警告。

原因 :package() 中 cp 命令误写了两个目标目录(重复的 $LYCIUM_ROOT/usr/$pkgname/$ARCH/lib/),cp 将第二个参数当作目录导致该警告。

解决 :只保留一个目标目录,例如:
cp -f CRC/$ARCH-build/libkomrad36_crc.a $LYCIUM_ROOT/usr/$pkgname/$ARCH/lib/


问题三:设备上运行 CRC32Test 报 "No such file or directory"(文件存在)

现象 :设备上 ls 能看到 CRC32Test,但执行 ./CRC32Test 提示 "No such file or directory"。

原因 :可执行文件为动态链接 ,其 ELF 中记录的动态链接器路径 (如 /lib/ld-musl-*.so)与设备上的实际路径不一致,内核在加载时找不到解释器,从而报"找不到文件"(实为找不到解释器)。

解决 :在 CMake 中为 CRC32Test 增加静态链接
set_target_properties(CRC32Test PROPERTIES LINK_FLAGS "-static")

重新编译后,将新的 CRC32Test 拷贝到设备再运行。


问题四:设备上运行 CRC32Test 报 Signal 7 或 Signal 11

现象 :在 aarch64 设备上运行 armeabi-v7a-build 下的 CRC32Test 报 Signal 7 (SIGBUS);或运行 arm64-v8a-build 下的 CRC32Test 报 Signal 11(SIGSEGV)。

原因

  • Signal 7:设备为 aarch64(64 位) ,却运行了 armeabi-v7a(32 位) 的二进制,架构不匹配。
  • Signal 11:测试中对 "123456789"(9 字节)调用了 option_9_tabular_8_bytesoption_10_tabular_16_bytes ,二者按 8/16 字节整块读取且不检查剩余长度,会越界导致 SIGSEGV。

解决

  • aarch64 设备上只运行 arm64-v8a-build 下的 CRC32Test,不要运行 armeabi-v7a-build。
  • 测试中仅调用 option_5 与 option_6(按字节处理),不调用 option_9/option_10;或仅对长度≥16 的缓冲区测试 option_10。

问题五:测试一直显示 "[FAIL] 123456789 expected 0xe3069283 got naive=0x58e3fa20 tabular=0x58e3fa20"

现象:已把 HPKBUILD 中期望值改为 0x58e3fa20,但设备上仍输出 "expected 0xe3069283"。

原因

期望值写错 :本库 CRC 使用初始值 0"123456789" 的正确结果是 0x58e3fa20 ;常见文档中的 0xe3069283 对应的是"初始值 0xFFFFFFFF"的 CRC-32C,不能混用。

设备上跑的是旧二进制 :HPKBUILD 中的测试源码是在 prepare() 里生成的;修改 HPKBUILD 后必须重新完整编译 (会先 clean 再解压、prepare、生成新的 main_test.cpp 并编译),再把新生成的 CRC32Test 拷贝到设备。若未重新拷贝,设备上仍是旧的 CRC32Test,其内嵌的期望值仍是 0xe3069283。

解决

① 在 HPKBUILD 的测试用例中,将 "123456789" 的期望值设为 0x58e3fa20 ,并在注释中说明"本实现使用初始值 0"。

② 修改 HPKBUILD 后执行一次完整 ./build.sh komrad36-CRC,从 thirdparty/komrad36-CRC/CRC-master/CRC/<ARCH>-build/CRC32Test 拷贝 的 CRC32Test 到设备后再执行。

③ 可在测试程序开头增加一行固定输出(如 printf("CRC32Test (init0, expect 123456789=0x58e3fa20)\n");),用于确认设备上运行的是新版本二进制。


六、设备侧测试简要步骤

  1. arm64-v8a-build (aarch64 设备)或 armeabi-v7a-build (32 位 ARM 设备)下的 CRC32Test 拷贝到开发板,例如:

    bash 复制代码
    hdc file send .../CRC-master/CRC/arm64-v8a-build/CRC32Test /data/local/tmp/
    hdc shell chmod +x /data/local/tmp/CRC32Test
  2. 在设备上执行:

    bash 复制代码
    /data/local/tmp/CRC32Test
  3. 通过时输出示例:

    text 复制代码
    CRC32Test (init0, expect 123456789=0x58e3fa20)
    [PASS] empty
    [PASS] 123456789
    All 2 tests passed.

七、总结

  • komrad36-CRC 在 OpenHarmony 上的交叉编译依赖 lycium 的 HPKBUILD/HPKCHECK;通过 prepare() 内 heredoc 生成 CMake、测试源码与头文件,无需维护独立 patch 文件,即可完成可移植子集的构建与测试。
  • 典型问题 :HPKBUILD 的 CRLF、heredoc 结束符、路径中 36 被解析为重定向导致路径被拆;设备上动态链接器路径不一致(No such file or directory)、架构/二进制不一致(Signal 7)、测试越界(Signal 11)、期望值与本库"初始值 0"不一致(0xe3069283 vs 0x58e3fa20)、以及修改 HPKBUILD 后未重新编译与拷贝导致的"旧二进制"现象,均可通过换行符统一、路径加引号、静态链接、仅测 option_5/6、修正期望值为 0x58e3fa20 并重新拷贝新二进制等方式规避。
  • 最终产物:lycium/usr/komrad36-CRC/<ARCH>/ 下的静态库与头文件可供应用集成;CRC-master/CRC/<ARCH>-build/CRC32Test 用于设备端功能验证。

若你在使用 lycium 接入其他 C/C++ 三方库时遇到"路径被拆""heredoc 解析错""设备上找不到可执行文件"或"测试期望值与实现约定不一致"等问题,可参考本文的排查思路与修改方式。更多 OpenHarmony 跨平台与三方库共建内容,欢迎在 开源鸿蒙跨平台社区 交流。


参考与相关链接

相关推荐
草莓熊Lotso1 小时前
Linux 程序地址空间深度解析:虚拟地址背后的真相
java·linux·运维·服务器·开发语言·c++·人工智能
郝学胜-神的一滴1 小时前
使用Linux命名管道(FIFO)实现无血缘关系进程间通信
linux·服务器·开发语言·c++·程序人生
HAPPY酷1 小时前
std::pair` 与 `std::map` 基础
开发语言·c++·算法
柒儿吖2 小时前
基于 lycium 在 OpenHarmony 上交叉编译 cppDES 完整实践
c++·harmonyos
俩毛豆2 小时前
获得2025年度鸿蒙开发者社区贡献榜单(问答产出榜、文章产出榜)
华为·harmonyos·鸿蒙
爱搞事的程小猿2 小时前
qml自定义扩展模块
c++·qt·qml
喜欢吃燃面2 小时前
基础算法:高精度
开发语言·c++·学习·算法
new_zhou2 小时前
Windows环境c++开发dump文件生成(优化方案)
c++·windows·qt
你的冰西瓜2 小时前
C++中的queue容器详解
开发语言·c++·stl