开源鸿蒙PC三方库复现:在 DevEco Studio 里调用三方 .so 库
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
开源仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_mediainfo_windows
当你手里已经有一个适配好的
.so和头文件,怎么在DevEco Studio的 Native C++ 工程里真正把它用起来。
本篇思路参考了社区作者wei_shuo的实践https://weishuo.blog.csdn.net/article/details/161196629,并以我自己的工程经验重新梳理。
这篇要解决的问题
交叉编译产出.so只是上半场。下半场是:让鸿蒙应用(ArkTS 写的 UI)能真正调到这个 C/C++ 库的能力。这中间隔着一层NAPI 桥接。我用libplacebo(一个视频渲染/色彩处理库)作为例子,它依赖极简、API 友好,很适合用来打通整条链路。
| 项目 | 信息 |
|---|---|
| IDE | DevEco Studio |
| 工程模板 | Native C++ |
| 示例库 | libplacebo v7.362.0 |
| 目标设备 | HUAWEI MateBook Pro(HarmonyOS) |
| 架构 | arm64-v8a |
我对整条调用链的理解
刚接触时我也被绕晕过,后来把它画成一条单向链路就清晰了:
ArkTS (Index.ets) → import 'libentry.so' → NAPI 层 (napi_init.cpp) 把 JS 调用转成 C++ 调用 → C++ 调用三方库 API (libplacebo) → 结果原路返回到 UI说白了,集成一个三方库,本质只要把三件事告诉构建系统:
- .so 在哪—— 打包时塞进 HAP,运行时能被加载。
- 头文件在哪—— 编译时 C++ 能
#include到 API 声明。 - 怎么链接—— 让自己的 native 模块和三方库产生符号引用。
记住这三件事,后面所有配置都是为它们服务的。
拿到产物先别急着放,先体检
这是我吃过亏才养成的习惯。拿到.so,先用 SDK 自带的llvm-readelf -d看两个字段:
- NEEDED:它运行时还依赖哪些 .so。系统自带的(
libc.so、libc++_shared.so)不用管;非系统的必须一起打包。 - SONAME:库的「内部名字」。
最隐蔽的坑:SONAME 带版本号
如果.so文件名是libplacebo.so,但内部 SONAME 是libplacebo.so.362,运行时dlopen会按 SONAME 去找libplacebo.so.362,结果找不到、加载失败。表现出来往往是个让人摸不着头脑的Cannot read property xxx of undefined。
我的修法是用 Python 直接在二进制里把 SONAME 字符串改成跟文件名一致,注意保持字节长度不变(用\x00补齐),不破坏 ELF 结构:
data=open('libplacebo.so','rb').read()data=data.replace(b'libplacebo.so.362\x00',b'libplacebo.so\x00\x00\x00\x00\x00')open('libplacebo.so','wb').write(data)改完再llvm-readelf -d复核一遍。这条经验对任何带版本号 SONAME 的库都通用。
文件该放哪
DevEco 的 Native C++ 工程有约定俗成的位置:
entry/ ├── libs/arm64-v8a/libplacebo.so ← .so 放这(按架构分目录) └── src/main/cpp/ ├── include/libplacebo/*.h ← 头文件放这(需手动建 include) ├── CMakeLists.txt ← 改 ├── napi_init.cpp ← 改 └── types/libentry/Index.d.ts ← 改 └── src/main/ets/pages/Index.ets ← 改放文件这一步不需要写任何代码,纯粹是「摆位置」。真正要动手改的是后面四个文件。
四个文件怎么改
1. CMakeLists.txt —— 告诉构建系统库在哪、怎么链
核心就是把三方库声明成一个IMPORTED(外部已有,不用编译)的库:
set(LIBS_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../libs/${OHOS_ARCH}) add_library(placebo SHARED IMPORTED) set_target_properties(placebo PROPERTIES IMPORTED_LOCATION ${LIBS_DIR}/libplacebo.so) include_directories(${NATIVERENDER_ROOT_PATH}/include) add_library(entry SHARED napi_init.cpp) target_link_libraries(entry PUBLIC libace_napi.z.so placebo)我自己记这段的逻辑:add_library(IMPORTED)是「声明有这么个外部库」,IMPORTED_LOCATION是「它在磁盘哪个位置」,include_directories解决头文件搜索,target_link_libraries把它和我的模块绑起来。换别的库,只要替换库名和文件名即可。
2. napi_init.cpp —— 写桥接函数
这一层是体力活,把每个想暴露给 ArkTS 的功能,写成一个napi_value函数,内部调用三方库 API,再用napi_create_string_utf8之类把结果转回 JS 类型。最后在Init里用napi_property_descriptor数组把它们注册出去。
我的建议是:先只暴露一个最简单的「拿版本号」函数把链路打通,确认能跑了再逐个加,别一上来写一大堆。
3. Index.d.ts —— 类型声明
给 native 模块写 TS 声明,函数名必须和 C++ 里注册的完全一致。不写也能跑,但 ArkTS 里调用会是any类型,没提示也没检查,体验很差。
4. Index.ets —— 前端调用
importplfrom'libentry.so';// ...this.report=pl.runAllTests();import xxx from 'libentry.so'这个写法第一次见会有点意外,记住「模块名 + .so 后缀」即可。
构建与真机验证
Build > Build Hap(s),然后 USB 连真机Run。几个高频报错我列一下对照:
| 报错 | 我的判断 |
|---|---|
config.h: No such file or directory | 头文件路径没配对,检查include_directories |
cannot find -lplacebo | .so路径不对,检查IMPORTED_LOCATION |
undefined reference to 'pl_version' | 链接漏了,检查target_link_libraries |
运行时Cannot read property xxx of undefined | 八成是 SONAME 没修 |
| 应用闪退 | .so的 NEEDED 依赖没打全 |
我提炼的通用流程
不管换成 OpenCV 还是 FFmpeg,这套步骤都成立:
1. llvm-readelf -d 检查 NEEDED 和 SONAME 2. 修 SONAME(带版本号的话) 3. .so → entry/libs/arm64-v8a/ 4. 头文件 → entry/src/main/cpp/include/ 5. CMakeLists.txt 声明 IMPORTED 库 6. napi_init.cpp 写桥接 7. Index.d.ts 写类型 8. Index.ets 导入调用 9. Build + Run小结
这一篇把「产物 → 应用可用」的最后一公里走通了。和上一篇的交叉编译合起来,就是一个三方库进入鸿蒙生态的完整闭环:编出来 → 调进去。
下一篇我会聊另一种集成场景——不在 DevEco,而是在鸿蒙 PC 的 CodeArts IDE 里直接编译、签名、运行 libplacebo。感谢原创适配作者wei_shuo的分享。