开源鸿蒙PC三方库实战复现:Java 本地访问库 JNA 的鸿蒙之路
开源鸿蒙PC三方库实战复现:Java 本地访问库 JNA 的鸿蒙之路
欢迎加入开源鸿蒙 PC 社区:https://harmonypc.csdn.net/
开源仓库地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_jna_pc
JNA(Java Native Access) 是个 Java 库,但它的核心有一块 C 写的本地调度库需要交叉编译。这类「Java 库里藏着 C」的适配,有它独特的门道。
本篇基于社区作者 程序山海 的《https://bigstar.blog.csdn.net/article/details/161798781》整理,结合我自己的理解重新讲述。致谢原作者的开源贡献。
先搞清楚:JNA 为什么需要交叉编译
JNA 的定位是让 Java 程序不写 JNI 代码就能调用本地共享库。在传统 JNI 里,你想让 Java 调一个 C 函数,得手写一堆胶水代码、编译成 so、再 System.loadLibrary,繁琐又易错。JNA 把这套自动化了——Java 里声明个接口,JNA 运行时帮你把调用映射到本地库函数上。它被 NetBeans、Eclipse、IntelliJ IDEA 这些重量级项目广泛使用,可见其可靠性。
既然是 Java 库,为什么要 lycium 来交叉编译 C?关键在于它有个核心组件 libjnidispatch.so——这是用 C 实现的「调度引擎」,负责三件底层脏活:
- Java 和本地 C/C++ 库之间的自动调度;
- 数据类型在 Java 与 C 之间的转换(比如 Java 的 String 怎么变成 C 的 char*);
- 函数调用的底层实现(参数压栈、调用约定等)。
这个 .so 是平台相关的,每个 CPU 架构 + 操作系统组合都要单独编一份。所以 JNA 适配的本质很清晰:为 arm64 鸿蒙编出一份 libjnidispatch.so,配上通用的 jna.jar 就能用了。
| 项目 | 信息 |
|---|---|
| 库 | JNA (Java Native Access) |
| 版本 | 5.14.0 |
| 协议 | LGPL-2.1 / Apache-2.0 |
| 构建系统 | Makefile |
| 依赖 | JDK 11、Ant、libffi |
| 架构 | arm64-v8a |

这次的几个全新挑战
JNA 带来了前面纯 C/C++ 项目里没遇到过的几类问题,挺有意思,也很能拓宽对「三方库适配」边界的认知。
挑战 1:X11 图形依赖 —— 无头系统的尴尬
JDK 里有个 jawt_md.h 头文件,它服务于 JAWT(Java AWT Native Interface,让本地代码能操作 Java 的 AWT 图形组件)。问题是这个头文件硬编码依赖 X11/Xlib.h。
可鸿蒙 PC 是个无头(headless)系统——它根本没有 X11 那套图形栈。于是只要编译路径碰到 JAWT,就会因为找不到 X11/Xlib.h 而失败。
我的处理思路:JNA 的 JAWT 功能在我们的场景里压根用不到(我们要的只是本地库调度能力,不需要操作 AWT 组件)。所以从源头禁用 JAWT,让编译根本不去碰那段依赖 X11 的代码。又一次回到系列里那条铁律——用不上的功能,禁掉比硬适配划算。
挑战 2:libffi 的「ohos 目标不认识」问题
JNA 依赖 libffi(Foreign Function Interface 库,提供运行时动态调用任意函数的能力,是 JNA 调度的基石)的静态库。
但坑在于:我用的 libffi 版本的 configure 脚本里,压根没有 ohos(鸿蒙)这个目标三元组。直接配 --host=aarch64-linux-ohos 会因为它不认识而失败。
解法很巧妙,利用了一个底层事实:鸿蒙的工具链和 Android 同源(都是基于 musl/clang 的类似思路)。所以可以借用 android 的兼容目标来配 libffi——让 configure 以为自己在为 android 编译(用 aarch64-linux-android 这类它认识的三元组),实际产出的 libffi 在鸿蒙上照样能用。这是个非常实用的「障眼法」,遇到老旧的 configure 脚本不认识鸿蒙时都可以试。
挑战 3:JNI 头文件要现场生成
JNA 编译需要 JNI 头文件(那些 com_sun_jna_xxx.h),但这些头文件不是源码自带的,要根据 Java 类用工具现场生成。流程是用 JDK 11 + Ant,通过 ant javah 这个任务自动生成。
所以构建环境里,JDK 11 和 Ant 是硬性前置要求,少一个都编不动。这也是为什么 JNA 的 makedepends 里要写上它们——lycium 会在构建前检查这些工具在不在。
挑战 4:Makefile 项目的链接陷阱
前面 MediaInfo/G’MIC 是 CMake,libplacebo/mpv 是 Meson,JNA 用的是更原始的 Makefile。这带来一个隐蔽的坑:
JNA 的 Makefile 默认靠 -shared 标志来生成共享库(.so)。但 lycium 的构建环境会设置 LDFLAGS 环境变量,而环境变量 LDFLAGS 会覆盖 Makefile 里写的默认值——结果 -shared 丢了,生成出来的东西就不对了。
处理时要特别留意环境变量和 Makefile 默认值的优先级关系:要么把 -shared 也加进我们设的 LDFLAGS 里,要么用 make 的参数方式传递避免被覆盖。Makefile 类项目这种「环境变量悄悄覆盖默认值」的问题很常见,是 CMake/Meson 那种结构化构建系统不太会有的。
又见熟悉的环境坑
跨过新挑战,几个「老朋友」照例还在:
- Windows 元数据:从 Windows 拷过来的源码带
:Zone.Identifier文件,打包前要清掉,否则可能混进产物或干扰构建。 - 构建缓存:改完配方清
hpk_build.csv,否则 lycium 跳过构建。 - CRLF 换行符:脚本带
\r会报$'\r': command not found,Python 二进制转 LF。
这些坑几乎每篇都出现,足见鸿蒙三方库适配的「环境基本功」有多重要——它们不难,但不处理就寸步难行。
一个重要的认知更正
我一开始想当然地以为:JNA 适配完,鸿蒙这边要不要也准备一堆头文件、配置之类。深入后才明白——JNA 是纯运行时库。
Java 端使用它的时候,不需要任何 C 头文件。开发者只要:
- 把我们交叉编译出的鸿蒙版
libjnidispatch.so放到合适位置; - 配上官方通用的
jna.jar;
就能在鸿蒙的 Java 应用里用 JNA 调本地库了。这让产物分发变得非常轻——一个 so + 一个 jar,干净利落。这也反过来解释了为什么这次适配的全部重心都在那个 libjnidispatch.so 上。
适配流程概览
把上面的点串起来,JNA 的适配大致是这样一条线:
1. 准备环境:JDK 11 + Ant + libffi 源码
2. libffi:借 android 目标交叉编译出静态库
3. ant javah:生成 JNI 头文件
4. 编 libjnidispatch.so:
- 禁用 JAWT,绕开 X11 依赖
- 处理 Makefile 的 LDFLAGS 覆盖问题,保住 -shared
- 链接上一步的 libffi
5. 清理 Windows 元数据,打包
6. 产物:libjnidispatch.so(配 jna.jar 使用)
小结
JNA 这一篇拓宽了整个系列的边界:原来「三方库适配」不只是纯 C/C++,跨语言库里的本地组件也是重要的一类,而且它们往往有自己独特的构建依赖(这里是 JDK/Ant)和坑(X11、libffi 目标、Makefile 覆盖)。
它带来的几个新工具值得记进工具箱:
- 禁用无用的图形依赖(JAWT/X11)——延续「做减法」原则;
- 借 android 目标编 libffi——应对老 configure 不认识鸿蒙;
- 用 ant javah 生成 JNI 头——跨语言库的特殊前置步骤;
- 警惕 Makefile 的 LDFLAGS 覆盖——原始构建系统的隐蔽陷阱。
从这一篇起,适配作者是 程序山海。下一篇他带来的是一个更复杂的 Linux 桌面服务——xdg-desktop-portal,难度再上一个台阶,还会引出鸿蒙运行时特有的 rpath 和文件权限问题。感谢原创适配作者 程序山海 的开源分享。
更多推荐




所有评论(0)