ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

hiredis移植到鸿蒙PC:交叉编译工具链与Native应用实践

hiredis移植到鸿蒙PC:交叉编译工具链与Native应用实践 把 hiredis 跑到鸿蒙PC上最早是我想在鸿蒙PC上做一个桌面数据管理工具时冒出来的需求。当时打开鸿蒙的Native能力文档发现标准C接口和POSIX网络调用基本都在心里就有底了这类优秀开源库大概率不用改多少源码用对的编译工具链一编就能出库。真正动手做三方库移植的人多半也是类似场景——要么是设备端采集程序要直连Redis要么是想在鸿蒙PC客户端里复用Linux生态中成熟的数据访问组件又或者只是想让自己的Native应用具备完整的Key-Value存储能力。这篇指南就围绕一件事hiredis在鸿蒙PC平台的完整移植过程从环境准备、工具链配置、源码编译到设备端跑通全流程走一遍顺便把其间踩过的坑和判断思路也交代清楚。1. 移植前先搞清楚这些鸿蒙PC环境与编译目标1.1 为什么需要把hiredis弄到鸿蒙PC上先从需求说起。鸿蒙PC虽然有自己的分布式数据管理能力但如果你要直连一个已经存在的Redis服务或者希望在多个设备之间共享一份以Redis为后端的业务缓存那么一个可靠的C客户端就是刚需。hiredis在Linux生态里的地位不用多介绍Redis官方出品几乎所有主流语言绑定的底层都是它协议解析、连接管理、同步异步接口都封装得干净利落而且依赖极窄特别适合作为三方库移植的入门对象。在鸿蒙PC上能走的路其实不多。系统自带的数据管理服务是给鸿蒙原生场景用的跨平台的老代码不会主动兼容它Javascript生态里的redis库倒是有一堆但绕不过Node那一层在C/C Native模块里直接调用非常别扭。把hiredis编成鸿蒙PC能直接加载的.so或.a你的C/C代码就能在Native层愉快地连接Redis后续接C SDK、Rust FFI都顺理成章。1.2 OpenHarmony SDK的工具链布局与host/target概念移植的第一步不是打开源码改代码而是先理解编译目标这件事。很多刚接触鸿蒙的开发者会在这一步晕掉明明自己的电脑是Windows为什么要用Linux风格的路径为什么编出来的二进制在本地跑不了这里要用到一组基本概念host和target。host是你当前用来做编译的机器通常是Windows、Linux或macOStarget是最终要运行程序的系统也就是鸿蒙PC通常是x86_64架构使用musl libc。鸿蒙的Native SDK给你提供了一套完整的LLVM/clang工具链和对应的sysrootsysroot里放的就是target系统所需的头文件和库文件。你本机的编译器默认是给host编译的必须显式告诉clang目标平台是谁否则编出来的东西跑不到鸿蒙上。我用的OpenHarmony SDK解压后典型的目录结构大概是这样不同版本可能有细微差异但大方向一致ohos-sdk/ └── linux/ └── native/ ├── llvm/ │ └── bin/ │ ├── clang │ └── llvm-readelf ├── sysroot/ │ └── usr/ │ ├── include/ │ └── lib/ └── build-tools/其中native/llvm/bin/clang是编译器本体native/sysroot是目标系统的根文件系统。编译的时候clang需要加两个关键参数--targetx86_64-linux-ohos指定目标三元组--sysroot路径指定头文件和库文件的根目录。如果你的目标是鸿蒙手机那种arm64设备三元组就换成aarch64-linux-ohos其他逻辑完全一样。1.3 验证基础编译环境在动hiredis之前我强烈建议先写一个helloworld把工具链环境验证通。这一步的性价比很高如果你连printf一个字符串都编不出来那后面hiredis编译出问题的时候你会完全分不清是库本身的问题还是工具链的问题。新建一个hello_ohos.c#include stdio.h int main(void) { printf(hello ohos pc\n); return 0; }然后手动执行编译命令。注意这里我假设你已经把SDK路径导出成了环境变量OHOS_SDK以下命令里的路径都基于这个变量展开export OHOS_SDK/path/to/ohos-sdk/linux/native ${OHOS_SDK}/llvm/bin/clang \ --targetx86_64-linux-ohos \ --sysroot${OHOS_SDK}/sysroot \ hello_ohos.c -o hello_ohos编译完之后用file命令看输出文件的格式file hello_ohos你会看到类似ELF 64-bit LSB executable, x86-64的输出并且动态链接器指向musl的loader。看到这个说明工具链已经通了目标编译环境没问题。如果file显示是x86-64但interpreter路径不对或者直接编出个host平台的ELF那就是--target或--sysroot没生效。这个验证步骤看起来简单但它确立了一个重要基准编译链是健康的。后面hiredis编出来的任何东西我们都能快速定位责任方。2. hiredis源码结构的适配面分析与移植方案2.1 hiredis的源码构成hiredis的源码量不大核心文件一眼能扫完。典型目录里会有这些文件hiredis/ ├── hiredis.c # 同步API、连接管理、命令执行 ├── hiredis.h ├── net.c # TCP连接、域名解析、超时设置 ├── net.h ├── read.c # RESP协议解析 ├── sds.c # 内部使用的动态字符串库 ├── sds.h ├── dict.c # 内部字典结构 ├── dict.h ├── async.c # 异步API依赖事件循环 ├── alloc.c # 内存分配钩子 ├── CMakeLists.txt └── Makefile从这个列表能看出来hiredis其实是一个自包含的库。它对外部依赖非常克制需要标准C库需要POSIX socket接口socket、connect、getaddrinfo、freeaddrinfo需要select或poll这类IO多路复用接口异步模块会用到仅此而已。没有强依赖openssl没有全局静态变量没有需要注册回调才能初始化的复杂状态机。这也是它为什么能在那么多平台上被移植的原因——社交圈很干净。2.2 依赖面分析为什么它好移植有朋友问我这块难不难。我的判断是hiredis这类库的移植难度天花板很低。它没有用到复杂的GNU扩展没有依赖glibc特有的接口也没有在编译期做太多平台相关的宏分支。它对libc的调用基本停留在malloc/free/string/socket这一档而鸿蒙PC的musl libc对POSIX接口覆盖得相当完整。换句话说只要编译链能把代码编成目标平台的ELF运行时基本不会出幺蛾子。需要留意的点主要有三个第一编译器版本问题。hiredis源码用的是C99风格老一点的版本甚至带着90年代的C语言味。现在SDK里的clang版本很新编译C99代码完全没压力但如果某些三方库用了GNU旧语法clang的默认编译模式可能会报错。解法就是编译时加-stdgnu99或-stdgnu11这个问题在hiredis里其实不存在我只是提醒你遇到同类库要注意。第二异步模块的IO多路复用。async.c里会在不同平台选择不同的机制宏控得比较多。如果只是想在鸿蒙PC上用同步API可以直接关掉异步相关的测试正常编库不会出问题。你后续如果自己写了event loop接入async接口才需要关心epoll/kqueue/select这些底层差异。第三TLS。默认的hiredis不带TLS需要TLS功能才要额外链接openssl。移植的第一步完全可以忽略TLS先让非加密连接跑通再说。等你把基础链路走顺了再考虑升级到SSL变体。2.3 方案选型独立编译产物 vs 编进系统镜像搞清楚依赖面之后就该定移植方案了。对于鸿蒙PC上的三方库移植常见有两套思路。方案A把hiredis当作独立产物用CMake加自定义工具链文件编出.so和.a。这个方案的好处是开发环境干净不依赖庞大的鸿蒙源码树出问题排查范围小而且产物可以直接丢进应用工程的Native目录。适合大多数应用开发者、SDK集成方。方案B把hiredis源码放进OpenHarmony系统源码里写一个BUILD.gn作为系统组件编进镜像。这个方案的好处是可以直接成为系统内建库其他系统组件引用方便但前提是你得有完整的鸿蒙系统源码环境和构建环境生产力门槛高不少。它适合整机方案商或者要深度集成进系统的团队。本文以方案A为主线因为它能让你在最短时间内拿到可用产物验证完移植流程后再考虑要不要进系统。方案B的思路我会在最后给一段至少让有心人知道往哪个方向走。3. 核心实操toolchain文件与编译命令3.1 编写ohos-x86_64的CMake工具链文件hiredis本身提供了CMakeLists.txt和传统Makefile两套构建入口。我的建议是直接用CMake原因很简单CMake能干净地指定工具链、sysroot、查找路径不会像Makefile那样在环境变量里磕磕绊绊。我第一次用自带Makefile时试图通过CCclang CFLAGS...指定目标结果它对CFLAGS的追加方式跟我想的不太一样折腾半天不如换CMake一下清爽。在hiredis根目录外面建一个工具链文件我命名为ohos_x86_64.toolchain.cmake内容如下# 鸿蒙PC x86_64 目标工具链 set(CMAKE_SYSTEM_NAME Linux) set(CMAKE_SYSTEM_PROCESSOR x86_64) # 这里从环境变量读取SDK路径方便切换版本 set(OHOS_SDK $ENV{OHOS_SDK}) set(TOOLCHAIN_PATH ${OHOS_SDK}/llvm) set(SYSROOT ${OHOS_SDK}/sysroot) set(CMAKE_C_COMPILER ${TOOLCHAIN_PATH}/bin/clang) set(CMAKE_CXX_COMPILER ${TOOLCHAIN_PATH}/bin/clang) set(TARGET_TRIPLE x86_64-linux-ohos) # 关键指定目标三元组和sysroot set(CMAKE_C_COMPILER_TARGET ${TARGET_TRIPLE}) set(CMAKE_CXX_COMPILER_TARGET ${TARGET_TRIPLE}) set(CMAKE_C_COMPILER_SYSROOT ${SYSROOT}) set(CMAKE_CXX_COMPILER_SYSROOT ${SYSROOT}) # 查找提醒只允许到sysroot里找库和头文件防止装到host的glibc set(CMAKE_FIND_ROOT_PATH ${SYSROOT}) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)几个关键点说一下。CMAKE_SYSTEM_NAME设为Linux是告诉CMake不要用宿主机系统检测逻辑去猜目标系统CMAKE_C_COMPILER_TARGET和CMAKE_C_COMPILER_SYSROOT是CMake正统的交叉编译变量比手动往CFLAGS里塞--target和--sysroot更安全因为CMake在测试编译器、检查头文件时会自动带上这些参数。CMAKE_FIND_ROOT_PATH的四个MODE设置是为了防止一个经典问题CMake在find_package时默认会优先搜宿主机的 /usr/include如果不限制很容易把glibc的头文件或host平台的库卷进来编出一个看似成功实际一运行就崩的怪胎。3.2 拉源码、配置、编译假设你已经把hiredis源码放到了~/work/hiredisSDK路径也导出了接下来就是标准的CMake三连cd ~/work cmake -S hiredis -B build_ohos \ -DCMAKE_TOOLCHAIN_FILEohos_x86_64.toolchain.cmake \ -DCMAKE_BUILD_TYPERelease \ -DDISABLE_TESTSON \ -DENABLE_EXAMPLESOFF \ -DCMAKE_POSITION_INDEPENDENT_CODEON cmake --build build_ohos -j$(nproc)DISABLE_TESTSON会跳过测试目标ENABLE_EXAMPLESOFF不编示例程序这两个选项能大幅减少编译耗时。CMAKE_POSITION_INDEPENDENT_CODEON是为了让静态库也带PIC方便后续链接进同一个so的时候不做额外重定位。编译顺利的话build_ohos目录下会出现libhiredis.so、libhiredis.a可能还有一个带版本号的软链接。编译结束一定要验证产物这一步不能跳过file build_ohos/libhiredis.so llvm-readelf -d build_ohos/libhiredis.so | grep NEEDED llvm-readelf -d build_ohos/libhiredis.so | grep SONAMEfile输出应该是ELF 64-bit LSB shared object, x86-64确认架构正确。readelf的NEEDED列表通常只包含libc.so如果出现libssl.so或其他奇怪依赖说明你意外打开了某些选项需要回查。SONAME字段则直接决定你部署so的时候该给它起什么名字后面踩坑环节会细说。3.3 关于动态库和静态库的选择这一步产物两种库都有实际用哪个取决于你的集成方式。动态库适合要多个模块共享一份hiredis代码的场景。体积小升级方便但部署时需要把so放到应用的Native库目录并且要注意SONAME和文件名的关系。鸿蒙应用工程的Native库目录一般按架构分比如libs/x86_64/或libs/arm64-v8a/你把so放进去应用加载时就能通过标准dlopen机制找到它。静态库则适合只想在单个可执行文件或单个so里用hiredis不想多一个外部依赖的场景。直接把libhiredis.a链接进工程省心。注意一个问题hiredis内部用了pthread的key接口链接可执行文件时有时需要显式加-lpthread。在musl libc环境里pthread通常已经并入libc但保险起见我的习惯还是在链接命令里带上-lpthread万一目标SDK的libc没有合并也不会出链接错。4. 让库真正跑起来部署验证与测试程序4.1 编写最小连接测试程序编译出来是一回事跑通是另一回事。这个阶段我习惯写一个最小的连接测试程序直接把整条链路——socket创建、TCP连接、RESP协议解析、内存释放——都过一遍。新建demo.c#include stdio.h #include hiredis/hiredis.h int main(void) { const char *host 192.168.1.10; int port 6379; redisContext *ctx redisConnect(host, port); if (ctx NULL) { printf(out of memory\n); return 1; } if (ctx-err) { printf(connect failed: %s\n, ctx-errstr); redisFree(ctx); return 1; } redisReply *reply redisCommand(ctx, SET %s %s, ohos:key, hello-hiredis); if (reply NULL) { printf(command failed: %s\n, ctx-errstr); redisFree(ctx); return 1; } printf(set reply: %s\n, reply-type REDIS_REPLY_STATUS ? reply-str : unexpected); freeReplyObject(reply); reply redisCommand(ctx, GET %s, ohos:key); if (reply reply-type REDIS_REPLY_STRING) { printf(get reply: %s\n, reply-str); } freeReplyObject(reply); redisFree(ctx); return 0; }编译时注意头文件路径和库路径${OHOS_SDK}/llvm/bin/clang \ --targetx86_64-linux-ohos \ --sysroot${OHOS_SDK}/sysroot \ -I ~/work/hiredis \ demo.c \ -L ~/work/build_ohos -lhiredis \ -o redis_demo如果链接时找不到hiredis头文件确认-I指向的是hiredis源码目录那是头文件的所在地-L和-l则指向编译产物目录。4.2 在鸿蒙PC环境部署运行测试程序编好之后要把它弄到鸿蒙PC设备或模拟器上执行。鸿蒙的调试工具是hdc用法和Android的adb类似。我的习惯是先把so和可执行文件都推到/data/local/tmp下这是设备端一个允许放临时文件的目录。hdc list targets hdc file send ~/work/build_ohos/libhiredis.so.1 /data/local/tmp/ hdc file send redis_demo /data/local/tmp/ hdc shell进入设备的shell后设置动态库搜索路径因为测试程序默认不会去/data/local/tmp找socd /data/local/tmp export LD_LIBRARY_PATH/data/local/tmp:$LD_LIBRARY_PATH chmod x redis_demo ./redis_demo这一步有朋友问过我为什么hdc file send之后还要chmod因为文件推过去默认可能没有可执行权限直接跑会报Permission denied这个问题几乎每个新人都踩过。花几秒钟chmod x能省掉一次无谓的排错。4.3 验证结果的解读如果一切正常程序会输出两行内容一行是SET命令返回的OK一行是GET命令取回的字符串。此时可以宣布hiredis在鸿蒙PC上从编译到运行全链路打通了。验证点要有意识地覆盖三条第一编译产物确实面向鸿蒙PC。前面file命令的架构检查已经完成。第二动态链接器能找到所有依赖。用llvm-readelf -d检查过NEEDED部署时LD_LIBRARY_PATH又设置了so路径加载不会失败。第三协议解析和TCP连接在musl环境下工作正常。SET/GET的结果就是最直接的证据。我可以顺手做个小实验多线程并发SET。hiredis的线程安全边界很清楚——不同线程各用各的redisContext互不干扰同一个连接不能同时被多个线程使用。所以写一个简单压测起8个线程每个线程自己redisConnect一次然后跑一万次SET观察有没有崩溃或命令交错。这类验证跑起来没有压力跑完心里就踏实了证明库在压力场景下的稳定性也可接受。5. 移植踩坑清单从so名字到运行时行为5.1 soname与动态库发布这个坑我在第一次发布so的时候踩得印象深刻。hiredis的CMake默认会给动态库设置版本信息编译产物目录里你会看到libhiredis.so - libhiredis.so.1 libhiredis.so.1 - libhiredis.so.1.1.0 libhiredis.so.1.1.0其中libhiredis.so.1.1.0是真实文件libhiredis.so和libhiredis.so.1都是软链接链接器找SONAME时依赖这个软链关系。问题在于很多人发布的时候只拷了裸的libhiredis.so或者只拷了带版本号的真实文件没把软链接一起带上。结果就是应用编译时能过运行时找不到libhiredis.so.1因为SONAME写的是带版本号的名字而设备上只有不带版本号的文件。解决方法是发布时连软链一起保留或者部署到设备后手动补软链ln -s libhiredis.so.1.1.0 libhiredis.so.1 ln -s libhiredis.so.1 libhiredis.so这个处理适用于任何带SONAME的三方库不只是hiredis。5.2 链接阶段的隐性问题用clang编hiredis的最终目标产物时还容易遇到一个链接期问题CMake在检测pthread时可能会去系统的lib路径找。我的工具链文件里设置了CMAKE_FIND_ROOT_PATH指向sysroot所以正常的SDK环境不会踩雷。但如果你发现链接报找不到-lpthread或者头文件里pthread相关宏没有定义多半是SDK版本里libc和libpthread的拆分方式有变化。这时候不要硬刚先看看sysroot/usr/lib下的实际库文件名再用-Wl,-l或直接把库路径写进link line。另一个链接期坑是符号版本问题。hiredis编译时如果链接了glibc的某些符号直接在musl环境跑会在启动时报版本错误。这个问题的根源往往是工具链文件没生效host的默认库被误用了。如果看到version GLIBC_ not found不是hiredis的错是你交叉编译环境没配干净。检查CMakeCache里的CMAKE_C_COMPILER_TARGET确认它真的是x86_64-linux-ohos。5.3 如果目标是整机镜像从CMake产物到BUILD.gn如果你最终要把hiredis编进鸿蒙PC系统镜像而不是只给应用用就需要从CMake迁移到gn构建体系。这里我提供一段参考模板思路是把源码以系统库形式编入import(//build/ohos.gni) ohos_shared_library(hiredis) { sources [ hiredis.c, net.c, read.c, sds.c, dict.c, async.c, alloc.c, ] include_dirs [ . ] output_name hiredis install_enable true }这个BUILD.gn放在hiredis源码目录下然后在你的产品配置里把它加到组件列表。注意不同OpenHarmony版本的ohos.gni接口细节可能有差异编不过时优先看同目录下其他系统库的gn文件怎么写的。我不建议把这条路径作为第一次移植的主线但作为进阶方向记录在这里等你在CMake路线把原理理解透了再接触gn会顺利得多。5.4 移植过程中的一条重要原则最后给一条贯穿始终的原则尽量不改源码。hiredis这种上游维护活跃的库任何对源码的改动都会给后续升级带来merge地狱。如果你发现某些地方确实要改优先用编译宏和配置项解决比如通过CMake选项控制编译哪些模块、关掉哪些特性。万不得已要patch源码也单独维护一个patch文件保留一份清晰的改动记录。我自己的习惯是建一个fork分支专门放针对鸿蒙的构建配置改动上游源码保持跟官方一致。这样做的好处是鸿蒙SDK升级了我可以先拉最新的官方tag把构建配置合进来再验证一遍。hiredis的接口几乎没大动过这样的升级路径非常平滑。顺着这个经验再多说一句移植三方库这件事本质上不是改代码而是配环境。大部分Linux生态的C库只要它依赖的是标准POSIX接口在鸿蒙上的适配工作九成都是构建层的活。你把CMake工具链、sysroot、动态库版本管理这几个基本功练扎实了以后遇到任何三方库都能用同一套方法论快速给出方案。鸿蒙PC的Native生态还在成长这类基础库的移植需求只会越来越多先跑通一个后面的路就好走了。
返回列表