开源鸿蒙PC三方库复现:在 DevEco Studio 里调用三方 .so 库
开源鸿蒙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 —— 前端调用
import pl from '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 的分享。
更多推荐



所有评论(0)