[鴻蒙PC三方庫適配]Java本地訪問庫JNA適配到鴻蒙PC平臺(tái)
本文是軟件鴻蒙化遷移實(shí)踐系列文章之一,專注于 Java JNI 本地庫的鴻蒙適配,為開發(fā)者提供完整的遷移指南。
歡迎加入開源鴻蒙PC社區(qū):https://harmonypc.csdn.net/
歡迎在PC社區(qū)平臺(tái)申請(qǐng)新建項(xiàng)目:https://atomgit.com/OpenHarmonyPCDeveloper
AtomGit 倉庫地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_jna_pc
項(xiàng)目信息說明
| 項(xiàng)目 | 說明 |
|---|---|
| 名稱 | JNA (Java Native Access) |
| 開源協(xié)議 | LGPL-2.1 / Apache-2.0 |
| 源碼版本 | 5.14.0 |
| 目標(biāo)平臺(tái) | 鴻蒙 PC |
| 依賴項(xiàng) | JDK 11, Ant, libffi |
| 操作系統(tǒng)平臺(tái) | WSL Ubuntu 24.04 |
一、背景介紹
1.0 功能與效果
JNA 本地庫在本實(shí)踐中的預(yù)期能力如下:
功能:為 Java 程序提供訪問本地共享庫的能力,無需編寫 JNI 代碼。JNA 通過 libjnidispatch.so 實(shí)現(xiàn) Java 與本地 C/C++ 庫之間的自動(dòng)調(diào)度。
效果:在鴻蒙 PC 上提供與標(biāo)準(zhǔn) Linux 環(huán)境相近的 JNA 使用體驗(yàn),便于在鴻蒙 Java 應(yīng)用開發(fā)中實(shí)現(xiàn)本地庫調(diào)用、系統(tǒng) API 訪問、硬件接口交互等場(chǎng)景的本地調(diào)度功能。
1.1 什么是 鴻蒙PC HNP 生態(tài)
HNP(Harmony Native Package)是 鴻蒙PC 的原生包格式,lycium 是增強(qiáng)型構(gòu)建框架,支持自動(dòng)下載源碼、交叉編譯(arm64-v8a、armeabi-v7a)、一鍵生成 HNP 包以及開源聲明聚合。C/C++ 原生庫的適配是鴻蒙系統(tǒng)生態(tài)建設(shè)中的重要一環(huán)。
為什么 JNA 需要 C/C++ 構(gòu)建?
雖然 JNA 是 Java 庫,但它包含一個(gè)核心的本地調(diào)度庫 libjnidispatch.so(C 代碼實(shí)現(xiàn))。這個(gè) so 文件負(fù)責(zé):
- Java 與本地 C/C++ 庫之間的自動(dòng)調(diào)度
- 數(shù)據(jù)類型轉(zhuǎn)換和內(nèi)存管理
- 函數(shù)調(diào)用的底層實(shí)現(xiàn)
因此,我們需要使用 lycium_plusplus 框架交叉編譯這個(gè) C 語言本地庫。
1.2 為什么適配 JNI 本地庫會(huì)有難度
常見挑戰(zhàn)包括:
- 構(gòu)建系統(tǒng)差異:JNA 使用 Makefile 構(gòu)建系統(tǒng),需要配置交叉編譯工具鏈(aarch64-linux-ohos-clang/clang++)。
- X11 圖形依賴:JDK 的 jawt_md.h 硬編碼需要 X11/Xlib.h,但 鴻蒙PC 是無頭系統(tǒng)(headless),不需要圖形界面。
- libffi 交叉編譯:JNA 依賴 libffi 靜態(tài)庫,老版本 libffi 不認(rèn)識(shí) ohos 目標(biāo),需要使用 android 兼容方案。
- JNI 頭文件生成:需要 JDK 11 和 Ant 工具,通過 ant javah 自動(dòng)生成 JNI 頭文件。
- 共享庫****鏈接配置:Makefile 默認(rèn)使用 -shared 標(biāo)志,但環(huán)境變量 LDFLAGS 會(huì)覆蓋 Makefile 的默認(rèn)值,需要特殊處理。
- Windows 元數(shù)據(jù)干擾:從 Windows 環(huán)境復(fù)制的源碼可能包含 :Zone.Identifier 等區(qū)域標(biāo)識(shí)文件,需要在打包前清理。
- 構(gòu)建緩存問題:lycium 使用 hpk_build.csv 跟蹤構(gòu)建狀態(tài),需要正確清理緩存才能重新構(gòu)建。
1.3 JNA 簡(jiǎn)介
JNA (Java Native Access) 是一款由 Sun Microsystems(現(xiàn) Oracle)開發(fā)的 Java 庫,提供訪問本地共享庫的能力而無需編寫 JNI 代碼。其主要特點(diǎn)包括:
- 零 JNI 代碼:Java 開發(fā)者無需編寫 C/C++ 代碼即可調(diào)用本地庫。
- 自動(dòng)類型映射:自動(dòng)處理 Java 類型與 C 類型之間的轉(zhuǎn)換。
- 跨平臺(tái)特性:原生支持 Windows、macOS、Linux,本次適配擴(kuò)展到 鴻蒙PC 平臺(tái)。
- 廣泛使用:被 NetBeans、Eclipse、IntelliJ IDEA 等知名項(xiàng)目使用。
- 開源地址:
- GitHub:https://github.com/java-native-access/jna
- AtomGit:https://atomgit.com/weixin_62765017/ohos_jna.git
二、環(huán)境準(zhǔn)備
2.1 系統(tǒng)要求
- 開發(fā)環(huán)境:Ubuntu 24.04(推薦 WSL 2)
- 核心工具:Make、GCC/G++、Git、Python3、JDK 11、Ant
- 構(gòu)建框架:lycium_plusplus
- 鴻蒙 SDK:鴻蒙PC SDK(提供交叉編譯工具鏈 aarch64-linux-ohos-clang/clang++)
- 目標(biāo)架構(gòu):arm64-v8a(AArch64)
2.2 擴(kuò)展閱讀與參考教程
下方匯總展示了多位老師在鴻蒙 鴻蒙PC 適配方面的高質(zhì)量教程。若在前提準(zhǔn)備(環(huán)境、工具鏈、框架)部分還有不清楚的地方,可參考這些文章進(jìn)一步學(xué)習(xí)。 以下資源不分先后順序,均具有參考價(jià)值。
| 資源類型 | 描述 | 鏈接 |
|---|---|---|
| 三方庫交叉編譯環(huán)境(Ubuntu) | 在 Ubuntu 中搭建鴻蒙PC 三方庫交叉編譯構(gòu)建開發(fā)環(huán)境 | ?? 點(diǎn)擊查看 |
| 三方庫交叉編譯環(huán)境(macOS) | 在 macOS 中搭建鴻蒙PC 三方庫交叉編譯開發(fā)環(huán)境 | ?? 點(diǎn)擊查看 |
| 基礎(chǔ)環(huán)境搭建 | Windows 10 上安裝和使用 WSL 2、安裝 Ubuntu 24 詳細(xì)指南 | ?? 點(diǎn)擊查看 |
| Mac 移植指南 | 鴻蒙PC命令行適配指南(Mac 版) | ?? 點(diǎn)擊查看 |
| Win 移植指南 | 鴻蒙PC 生態(tài)三方軟件移植:開發(fā)環(huán)境搭建及三方庫移植指南 | ?? 點(diǎn)擊查看 |
| 全流程適配指南 | OpenHarmony Linux 命令行工具適配實(shí)戰(zhàn):基于 Cursor × WSL 的 tree 2.2.1 交叉編譯與 HNP 打包全流程指南 | ?? 點(diǎn)擊查看 |
| 官方構(gòu)建文檔 | 新腳手架:社區(qū)維護(hù)的鴻蒙PC 生態(tài)命令行工具構(gòu)建框架 lycium_plusplus(原 build 倉庫為舊方式,請(qǐng)以本倉庫為準(zhǔn)) | ?? 點(diǎn)擊查看 |
2.3 配置 鴻蒙PC SDK 環(huán)境變量
在開始之前,需要先配置 鴻蒙PC SDK 路徑。這是所有后續(xù)操作的基礎(chǔ)。
# 配置 鴻蒙PC SDK 環(huán)境變量(請(qǐng)根據(jù)實(shí)際路徑修改)
export OHOS_SDK=/home/weishuo/ohos-sdk/linux
# 驗(yàn)證 SDK 是否存在
ls ${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang
# 應(yīng)該輸出:/home/weishuo/ohos-sdk/linux/native/llvm/bin/aarch64-linux-ohos-clang
注意:如果 SDK 路徑不同,請(qǐng)修改為你的實(shí)際路徑。
2.4 lycium_plusplus 框架
lycium_plusplus 是本次適配工作的核心工具,主要用于統(tǒng)一管理各類第三方庫的構(gòu)建流程,通過規(guī)范編譯、依賴與打包邏輯,實(shí)現(xiàn)三方庫在目標(biāo)平臺(tái)上高效、穩(wěn)定地編譯與集成,是整個(gè)適配環(huán)節(jié)中保障構(gòu)建一致性與可維護(hù)性的關(guān)鍵支撐。
# 克隆 lycium_plusplus 項(xiàng)目 git clone https://gitcode.com/OpenHarmonyPCDeveloper/lycium_plusplus.git cd lycium_plusplus
2.5 JDK 11 與 Ant 安裝
由于 JNA 需要生成 JNI 頭文件,需要安裝 JDK 11 和 Ant 工具。
# 安裝 JDK 11 sudo apt-get install -y openjdk-11-jdk # 安裝 Ant sudo apt-get install -y ant # 驗(yàn)證安裝 java -version javac -version ant -version # 配置 鴻蒙PC SDK 環(huán)境變量 export OHOS_SDK=/home/weishuo/ohos-sdk/linux
三、實(shí)戰(zhàn):以 JNA 為例的適配步驟
本章節(jié)將為新人開發(fā)者提供完整的、可復(fù)現(xiàn)的適配步驟。我們將從零開始,逐步完成 JNA 本地庫的鴻蒙適配工作。每個(gè)步驟都包含詳細(xì)的說明、命令示例和注意事項(xiàng)。
3.1 創(chuàng)建項(xiàng)目目錄結(jié)構(gòu)
步驟說明:
在 lycium_plusplus 框架中,每個(gè)三方庫都需要在 thirdparty/ 目錄下?lián)碛歇?dú)立的目錄。這個(gè)目錄將存放該庫的所有構(gòu)建配置文件(HPKBUILD、hnp.json、README.OpenSource、HPKCHECK 等)。
詳細(xì)操作流程:
# 1. 進(jìn)入 lycium_plusplus 項(xiàng)目根目錄 cd /home/weishuo/lycium_plusplus # 2. 進(jìn)入 thirdparty 目錄 cd thirdparty # 3. 創(chuàng)建 jna 目錄 mkdir -p jna # 4. 進(jìn)入新創(chuàng)建的目錄 cd jna # 5. 驗(yàn)證目錄創(chuàng)建成功 pwd # 輸出:/home/weishuo/lycium_plusplus/thirdparty/jna # 6. 查看目錄結(jié)構(gòu) ls -la
目錄結(jié)構(gòu)說明:
lycium_plusplus/
├── thirdparty/
│ ├── jna/ ← 我們剛創(chuàng)建的目錄
│ │ ├── HPKBUILD ← 將要?jiǎng)?chuàng)建的構(gòu)建腳本
│ │ ├── hnp.json ← 將要?jiǎng)?chuàng)建的包元數(shù)據(jù)
│ │ ├── README.OpenSource ← 將要?jiǎng)?chuàng)建的開源聲明
│ │ └── HPKCHECK ← 將要?jiǎng)?chuàng)建的檢查腳本
│ ├── mediainfo/ ← 其他三方庫示例
│ ├── fuse3/ ← 其他三方庫示例
│ └── ...
├── lycium/
│ ├── build.sh ← lycium 構(gòu)建入口腳本
│ └── usr/ ← 構(gòu)建產(chǎn)物輸出目錄
└── Projects/
└── jna/ ← JNA 源碼目錄(需提前準(zhǔn)備)
注意事項(xiàng):
- 目錄名稱必須與 pkgname 變量保持一致(本例中為 jna)
- 確保 Projects/jna/ 目錄中已經(jīng)有 JNA 的源碼
- 如果源碼還未準(zhǔn)備,需要先下載或克隆源碼到 Projects/jna/ 目錄(參考 3.0 節(jié))
3.2 禁用 JAWT 依賴的 sed 方案(推薦)
# 1. 在 #include <wchar.h> 后添加 NO_JAWT 宏定義 sed -i '/#include <wchar.h>/a\ \ /* OpenHarmony: Disable JAWT to avoid X11 dependency */\ #ifndef NO_JAWT\ #define NO_JAWT 1\ #endif' native/dispatch.c # 2. 將 #include <jni.h> 替換為直接包含 JNI 頭文件(跳過 jni_md.h) sed -i 's/^#include <jni.h>$/#include "com_sun_jna_Native.h"\n#include "com_sun_jna_Function.h"/' native/dispatch.c # 3. 修改 JAWT 條件編譯(禁用 JAWT 代碼) sed -i 's/^#ifndef NO_JAWT$/#ifdef DISABLE_JAWT_COMPLETELY/' native/dispatch.c
為什么推薦 sed 而不是 patch?
| 方式 | 優(yōu)點(diǎn) | 缺點(diǎn) |
|---|---|---|
| patch 文件 | 直觀、易讀 | 依賴行號(hào),版本不同可能失敗 |
| sed 命令 | 靈活、不依賴行號(hào) | 語法稍復(fù)雜 |
本文選擇:使用 sed 命令,直接在 HPKBUILD 的 prepare() 函數(shù)中修改源碼,無需額外創(chuàng)建 patch 文件。
注意事項(xiàng):
- 這三條 sed 命令會(huì)在后面的 HPKBUILD 的 prepare() 函數(shù)中自動(dòng)執(zhí)行
- 你不需要手動(dòng)運(yùn)行這些命令,只需要理解它們的原理即可
3.3 創(chuàng)建 HPKBUILD 文件(核心構(gòu)建腳本)
什么是 HPKBUILD?
HPKBUILD 是 lycium 框架的核心構(gòu)建腳本,可以理解為一個(gè)"構(gòu)建配方"。它告訴 lycium 框架:
- 這個(gè)庫叫什么、什么版本、什么許可證(元信息)
- 如何準(zhǔn)備源碼、如何編譯、如何打包(構(gòu)建流程)
- 使用什么編譯器和編譯參數(shù)(環(huán)境配置)
創(chuàng)建方法:
#!/bin/bash
# -----------------------------------------------------------------------------
# JNA (Java Native Access) HPKBUILD - OpenHarmony 鴻蒙適配
# 適配本地庫 libjnidispatch.so
# -----------------------------------------------------------------------------
pkgname=jna
pkgver=5.14.0
pkgrel=0
pkgdesc="Java Native Access - native dispatch library for OpenHarmony"
url="https://github.com/java-native-access/jna"
archs=("arm64-v8a")
license=("LGPL-2.1")
depends=()
makedepends=()
autounpack=false
downloadpackage=false
buildtools="make"
srcpath="${LYCIUM_ROOT}/../Projects/jna"
builddir="jna-${pkgver}"
# -----------------------------------------------------------------------------
# prepare():準(zhǔn)備源碼
# -----------------------------------------------------------------------------
prepare() {
if [ -d "$srcpath" ]; then
echo "Using local source from: $srcpath"
mkdir -p "$builddir"
cp -rf "$srcpath"/* "$builddir/"
# 清理 Windows 元數(shù)據(jù)
find "$builddir" -name "*.bak" -type f -delete 2>/dev/null || true
find "$builddir" -name "*:Zone.Identifier" -type f -delete 2>/dev/null || true
# 修改 dispatch.c:禁用 JAWT 避免 X11 依賴(OpenHarmony 不需要圖形界面)
cd "$builddir"
if [ -f "native/dispatch.c" ]; then
echo "Patching dispatch.c to disable JAWT..."
# 在 #include <wchar.h> 后添加 NO_JAWT 定義
sed -i '/#include <wchar.h>/a\
\
/* OpenHarmony: Disable JAWT to avoid X11 dependency */\
#ifndef NO_JAWT\
#define NO_JAWT 1\
#endif' native/dispatch.c
# 將 #include <jni.h> 替換為直接包含 JNI 頭文件(跳過 jni_md.h 的 X11 依賴)
sed -i 's/^#include <jni.h>$/#include "com_sun_jna_Native.h"\n#include "com_sun_jna_Function.h"/' native/dispatch.c
# 將 #ifndef NO_JAWT 改為 #ifdef DISABLE_JAWT_COMPLETELY(永不成立)
sed -i 's/^#ifndef NO_JAWT$/#ifdef DISABLE_JAWT_COMPLETELY/' native/dispatch.c
echo "dispatch.c patched successfully"
fi
cd "$OLDPWD"
# 生成 JNI 頭文件(關(guān)鍵步驟?。?
echo "Generating JNI headers..."
cd "$builddir"
ant javah > "${LYCIUM_ROOT}/log/jna-javah.log" 2>&1
ret=$?
if [ $ret -ne 0 ]; then
echo "ERROR: Failed to generate JNI headers"
cat "${LYCIUM_ROOT}/log/jna-javah.log" >&2
cd "$OLDPWD"
return $ret
fi
# 復(fù)制 JNI 頭文件到 build/native 目錄(Makefile 期望的位置)
mkdir -p build/native
if [ -d "build/headers" ]; then
cp -f build/headers/*.h build/native/
echo "JNI headers copied to build/native/"
ls -la build/native/*.h
fi
echo "JNI headers generated successfully"
cd "$OLDPWD"
echo "Prepare completed in: $builddir"
else
echo "ERROR: Source not found at $srcpath"
exit 1
fi
}
# -----------------------------------------------------------------------------
# build():編譯構(gòu)建
# -----------------------------------------------------------------------------
build() {
cd "$builddir/native"
# 設(shè)置 buildlog
buildlog="${LYCIUM_ROOT}/log/${pkgname}-build.log"
mkdir -p "${LYCIUM_ROOT}/log"
# 設(shè)置 OpenHarmony 交叉編譯工具鏈
export CC="${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang"
export CXX="${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang++"
export AR="${OHOS_SDK}/native/llvm/bin/llvm-ar"
export RANLIB="${OHOS_SDK}/native/llvm/bin/llvm-ranlib"
export STRIP="${OHOS_SDK}/native/llvm/bin/llvm-strip"
export NM="${OHOS_SDK}/native/llvm/bin/llvm-nm"
# 設(shè)置編譯標(biāo)志(注意:不要設(shè)置 LDFLAGS 環(huán)境變量,讓 Makefile 使用自己的默認(rèn)值)
# 添加 JNI 頭文件路徑(build/headers 和 build/native 都要包含)
# 添加 JDK include 路徑(jni.h 和 jni_md.h)
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export CFLAGS="--target=aarch64-linux-ohos --sysroot=${OHOS_SDK}/native/sysroot -O2 -fPIC -fno-strict-aliasing -I../build/native/libffi/include -I../build/native -I../build/headers -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux"
# 設(shè)置 libffi 交叉編譯參數(shù)(關(guān)鍵?。?
# 使用 aarch64-linux-android 因?yàn)槔习姹?libffi 不認(rèn)識(shí) ohos
export FFI_CONFIG="--enable-static --disable-shared --with-pic=yes --host=aarch64-linux-android"
export FFI_ENV="CC=\"$CC\" CFLAGS=\"$CFLAGS\" CPPFLAGS=\"$CFLAGS\""
export FFI_BUILD="../build/native/libffi"
# 創(chuàng)建構(gòu)建目錄
mkdir -p ../build/native
# 使用 Makefile 構(gòu)建(參考 Android 交叉編譯方式)
make clean > "$buildlog" 2>&1 || true
# make clean 會(huì)刪除 build/native 目錄,需要重新復(fù)制 JNI 頭文件
mkdir -p ../build/native
if [ -d "../build/headers" ]; then
cp -f ../build/headers/*.h ../build/native/
echo "JNI headers re-copied to build/native/ after clean"
fi
make \
OS=linux \
ARCH=aarch64 \
CC="$CC" \
CXX="$CXX" \
AR="$AR" \
RANLIB="$RANLIB" \
STRIP="$STRIP" \
CFLAGS="$CFLAGS" \
CPPFLAGS="-DNO_JAWT -DNO_WEAK_GLOBALS -DFFI_STATIC_BUILD" \
CDEFINES="-DFFI_STATIC_BUILD -DNO_JAWT -DNO_WEAK_GLOBALS -DFFI_MMAP_EXEC_WRIT=1 -DFFI_MMAP_EXEC_SELINUX=0" \
HOST_CONFIG="--host=aarch64-linux-android" \
FFI_CONFIG="--enable-static --disable-shared --with-pic=yes --host=aarch64-linux-android" \
JAVA_HOME="" \
JAVAH="../build/native" \
BUILD="../build/native" \
INSTALLDIR="../build/linux-aarch64" \
>> "$buildlog" 2>&1
ret=$?
if [ $ret -ne 0 ]; then
echo "Make build failed!"
cat "$buildlog" >&2
cd "$OLDPWD"
return $ret
fi
cd "$OLDPWD"
return $ret
}
# -----------------------------------------------------------------------------
# check():驗(yàn)證構(gòu)建產(chǎn)物
# -----------------------------------------------------------------------------
check() {
echo "The test must be on an OpenHarmony device!"
}
# -----------------------------------------------------------------------------
# package():打包產(chǎn)物
# -----------------------------------------------------------------------------
package() {
: ${destdir:=${LYCIUM_ROOT}/usr/${pkgname}/${ARCH}}
# 只復(fù)制 libjnidispatch.so
mkdir -p "${destdir}/usr/lib"
_lib_path="${LYCIUM_ROOT}/../thirdparty/${pkgname}/${builddir}/build/native"
if [ -f "$_lib_path/libjnidispatch.so" ]; then
cp -f "$_lib_path/libjnidispatch.so" "${destdir}/usr/lib/"
chmod 755 "${destdir}/usr/lib/libjnidispatch.so"
echo " ? Installed libjnidispatch.so"
else
echo " ? libjnidispatch.so not found at $_lib_path"
return 1
fi
# 清理不需要的文件
find "${destdir}" -name "*.bak" -type f -delete 2>/dev/null || true
find "${destdir}" -name "*:Zone.Identifier" -type f -delete 2>/dev/null || true
return 0
}
# -----------------------------------------------------------------------------
# archive():生成歸檔包
# -----------------------------------------------------------------------------
archive() {
export HNP_TOOL="${HNP_TOOL:-${OHOS_SDK}/toolchains/hnpcli}"
mkdir -p ${LYCIUM_ROOT}/output/$ARCH
# 打包 tar.gz
pushd ${LYCIUM_ROOT}/usr/${pkgname}/${ARCH} > /dev/null 2>&1
tar -zcf ${LYCIUM_ROOT}/output/$ARCH/${pkgname}_${pkgver}.tar.gz .
echo "Archive completed: ${LYCIUM_ROOT}/output/$ARCH/${pkgname}_${pkgver}.tar.gz"
popd > /dev/null 2>&1
# 打包 HNP
if [ -f "${HNP_TOOL}" ]; then
cp ${LYCIUM_ROOT}/../thirdparty/${pkgname}/hnp.json ${LYCIUM_ROOT}/usr/${pkgname}/${ARCH}/
${HNP_TOOL} pack \
-i ${LYCIUM_ROOT}/usr/${pkgname}/${ARCH} \
-o ${LYCIUM_ROOT}/output/$ARCH/
echo "Archive completed: ${LYCIUM_ROOT}/output/$ARCH/${pkgname}.hnp"
else
echo "Warning: hnpcli not found at ${HNP_TOOL}, skipping HNP generation"
fi
}
# -----------------------------------------------------------------------------
# cleanbuild():清理構(gòu)建產(chǎn)物
# -----------------------------------------------------------------------------
cleanbuild() {
echo "Cleaning build artifacts for ${pkgname}..."
# 清理構(gòu)建目錄
rm -rf "${LYCIUM_ROOT}/../thirdparty/${pkgname}/${builddir}"
# 清理 output
rm -rf "${LYCIUM_ROOT}/output/${pkgname}"*
# 清理 usr 產(chǎn)物
rm -rf "${LYCIUM_ROOT}/usr/${pkgname}"
echo "Clean completed"
}
HPKBUILD 核心結(jié)構(gòu)說明:
- 第 1 部分:元信息:庫名稱定義為 jna,運(yùn)行時(shí)依賴為空,JNA 無需額外 HNP 依賴包
- 第 2 部分:prepare () 準(zhǔn)備函數(shù):完成源碼復(fù)制至編譯目錄,關(guān)閉 JAWT 組件以規(guī)避 X11 依賴,通過 ant javah 指令編譯生成 JNI 頭文件
- 第 3 部分:build () 構(gòu)建函數(shù):配置 aarch64-linux-ohos-clang 交叉編譯工具鏈,采用安卓主機(jī)三元組編譯 libffi 靜態(tài)庫,最終完成 libjnidispatch.so 動(dòng)態(tài)庫編譯
- 第 4 部分:package () 打包函數(shù):將編譯產(chǎn)出的 libjnidispatch.so 庫文件,拷貝至系統(tǒng) usr/lib 目錄完成部署
- 第 5 部分:archive () 歸檔函數(shù):輸出 tar.gz 格式壓縮包,檢測(cè) hnpcli 工具是否存在,按需同步生成 hnp 安裝包
構(gòu)建流程:
prepare() → 準(zhǔn)備源碼、修改代碼、生成 JNI 頭文件
↓
build() → 交叉編譯 libffi、編譯 libjnidispatch.so
↓
package() → 安裝 .so 文件到 usr/lib/
↓
archive() → 生成 tar.gz 和 hnp 包

3.4 創(chuàng)建 jna-disable-jawt.patch 補(bǔ)丁文件
什么是 patch 文件?
patch 文件是用于修改源碼的文本文件,記錄了需要修改的文件位置和修改內(nèi)容。在 JNA 適配中,我們需要通過 patch 修改 dispatch.c 來禁用 JAWT 依賴。
為什么需要這個(gè)補(bǔ)丁:
- X11 依賴問題:JDK 的 jawt_md.h 硬編碼包含 #include <X11/Xlib.h>
- 鴻蒙PC 無 X11:鴻蒙PC 是無頭系統(tǒng)(headless),沒有圖形界面,不提供 X11 庫
- JAWT 非必需:JNA 的核心功能不需要 JAWT,只有需要 Java GUI 組件才需要
- 編譯阻斷:不修改會(huì)導(dǎo)致編譯錯(cuò)誤 fatal error: ‘X11/Xlib.h’ file not found
創(chuàng)建方法:
--- a/native/dispatch.c +++ b/native/dispatch.c @@ -111,9 +111,15 @@ #include <stdlib.h> #include <wchar.h> -#include <jni.h> + +/* OpenHarmony: Disable JAWT to avoid X11 dependency */ +#ifndef NO_JAWT +#define NO_JAWT 1 +#endif + +#include "com_sun_jna_Native.h" +#include "com_sun_jna_Function.h" -#ifndef NO_JAWT +#ifdef DISABLE_JAWT_COMPLETELY #include <jawt.h> #include <jawt_md.h> #endif
補(bǔ)丁修改說明:
- 第一處修改:在 #include <jni.h> 之前定義 NO_JAWT 宏
- 這個(gè)宏會(huì)告知 JNA 代碼禁用 JAWT 相關(guān)功能
- 第二處修改:將 #include <jni.h> 替換為直接包含 JNI 頭文件
- 跳過 jni.h 間接包含 jawt_md.h 的鏈條
- 直接包含 com_sun_jna_Native.h 和 com_sun_jna_Function.h
- 第三處修改:將 #ifndef NO_JAWT 改為 #ifdef DISABLE_JAWT_COMPLETELY
- DISABLE_JAWT_COMPLETELY 宏永遠(yuǎn)不定義,所以 JAWT 代碼永遠(yuǎn)不會(huì)編譯
使用方式:
# 在 HPKBUILD 的 prepare() 中應(yīng)用 patch cd "$builddir" patch -p1 < "../jna-disable-jawt.patch"
注意事項(xiàng):
- patch 文件可能因 JNA 版本不同而需要調(diào)整行號(hào)
- 本文 HPKBUILD 示例使用 sed 方式(更靈活),無需 patch 文件
- 如果你更喜歡使用 patch 文件,可以參考本節(jié)的創(chuàng)建方法

3.5 創(chuàng)建 hnp.json(包元數(shù)據(jù))
什么是 hnp.json?
hnp.json 是鴻蒙 HNP 包的元數(shù)據(jù)文件,類似于 Node.js 的 package.json。它告訴系統(tǒng):
- 這個(gè)包叫什么、什么版本、什么許可證
- 包含哪些文件、安裝到哪些目錄
- 依賴哪些其他包
創(chuàng)建方法:
{
"type": "hnp-config",
"name": "jna-native",
"version": "5.14.0",
"description": "Java Native Access native library for OpenHarmony",
"license": "LGPL-2.1",
"arch": "arm64-v8a",
"install": {
"lib": ["usr/lib/libjnidispatch.so"]
}
}
字段說明:
- type:固定為 “hnp-config”
- name:包名稱(可以與 pkgname 不同)
- version:版本號(hào),與 HPKBUILD 中的 pkgver 一致
- arch:目標(biāo)架構(gòu),arm64-v8a 表示 64 位 ARM
- install.lib:要安裝的庫文件列表

3.6 創(chuàng)建 README.OpenSource(開源聲明)
什么是 README.OpenSource?
README.OpenSource 是開源合規(guī)聲明文件,記錄:
- 使用了哪些開源組件
- 每個(gè)組件的許可證類型
- 上游源碼地址和版本
這是鴻蒙生態(tài)的必備文件,用于滿足開源許可證的法律要求。
創(chuàng)建方法:
[
{
"Name": "JNA (Java Native Access)",
"License": "LGPL-2.1",
"License File": "https://github.com/java-native-access/jna/blob/master/LICENSE",
"Version Number": "5.14.0",
"Owner": "your-email@example.com",
"Upstream URL": "https://github.com/java-native-access/jna",
"Description": "JNA provides Java programs easy access to native shared libraries without writing JNI code. This package contains the native dispatch library (libjnidispatch.so) for OpenHarmony."
}
]
注意:雖然文件名是 .OpenSource,但內(nèi)容必須是合法的 JSON 數(shù)組格式。

3.7 創(chuàng)建 HPKCHECK 檢查腳本
什么是 HPKCHECK?
HPKCHECK 是自動(dòng)驗(yàn)證腳本,在構(gòu)建后檢查:
- 產(chǎn)物是否存在(.so 文件)
- 文件格式是否正確(ELF)
- 架構(gòu)是否匹配(ARM64)
- 是否使用 musl libc
#!/bin/bash
HPK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "${HPK_DIR}" || exit 1
source ./HPKBUILD > /dev/null 2>&1
logfile="${HPK_DIR}/${pkgname}_${ARCH}_test.log"
checkprepare() {
return 0
}
openharmonycheck() {
res=0
inst_lib="${LYCIUM_ROOT}/usr/${pkgname}/${ARCH}/lib"
if [ -d "${inst_lib}" ]; then
echo "start test times: $(date)" >> "${logfile}" 2>&1
# 檢查 libjnidispatch.so
if [ -f "${inst_lib}/libjnidispatch.so" ]; then
echo "? libjnidispatch.so exists" >> "${logfile}" 2>&1
# 檢查 ELF 格式
file "${inst_lib}/libjnidispatch.so" >> "${logfile}" 2>&1
ret=$?
res=$(( res | $ret ))
# 檢查架構(gòu)
if grep -q "ARM aarch64" "${logfile}"; then
echo "? Architecture is ARM64" >> "${logfile}" 2>&1
else
echo "? Architecture mismatch" >> "${logfile}" 2>&1
res=1
fi
# 檢查 musl
if grep -q "musl" "${logfile}"; then
echo "? Using musl libc" >> "${logfile}" 2>&1
else
echo "?? Not using musl libc" >> "${logfile}" 2>&1
fi
else
echo "? libjnidispatch.so not found" >> "${logfile}" 2>&1
res=1
fi
echo "end test times: $(date)" >> "${logfile}" 2>&1
else
echo "? ${inst_lib} directory not found" >> "${logfile}" 2>&1
res=1
fi
return $res
}
步驟說明:
HPKCHECK 是 lycium 框架的構(gòu)建檢查腳本,用于在編譯前驗(yàn)證環(huán)境是否滿足構(gòu)建要求,以及在編譯后驗(yàn)證產(chǎn)物是否正確。它相當(dāng)于一個(gè)"自動(dòng)化測(cè)試腳本",確保構(gòu)建質(zhì)量。
HPKCHECK 的作用:
- 環(huán)境驗(yàn)證:檢查編譯環(huán)境是否滿足要求
- 產(chǎn)物驗(yàn)證:驗(yàn)證生成的庫文件是否存在且格式正確
- 架構(gòu)檢查:驗(yàn)證 ELF 文件格式和目標(biāo)架構(gòu)
- 日志記錄:記錄測(cè)試結(jié)果到日志文件

四、編譯流程與完整示例
4.1 環(huán)境準(zhǔn)備
配置用于前置環(huán)境檢查與變量設(shè)置,通過指定 鴻蒙PC SDK 路徑、驗(yàn)證 JDK 和 Ant 環(huán)境,為 lycium_plusplus 構(gòu)建 JNA 三方庫提供基礎(chǔ)運(yùn)行環(huán)境
# 1. 確保 鴻蒙PC SDK 已安裝
export OHOS_SDK=/home/weishuo/ohos-sdk/linux
# 2. 確保 JDK 11 和 Ant 已安裝
java -version
javac -version
ant -version
# 3. 驗(yàn)證交叉編譯工具鏈
ls ${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang
# 應(yīng)該輸出:/home/weishuo/ohos-sdk/linux/native/llvm/bin/aarch64-linux-ohos-clang
4.2 創(chuàng)建項(xiàng)目結(jié)構(gòu)并執(zhí)行編譯
通過目錄操作、腳本創(chuàng)建、清理緩存、執(zhí)行構(gòu)建指令,完成 lycium_plusplus 中 JNA 三方庫的從零構(gòu)建全流程
# 1. 進(jìn)入 thirdparty 目錄并創(chuàng)建 jna 目錄 cd /home/weishuo/lycium_plusplus/thirdparty mkdir -p jna cd jna # 2. 創(chuàng)建 4 個(gè)核心文件(內(nèi)容見第三章) # HPKBUILD、hnp.json、README.OpenSource、HPKCHECK # 可以使用 cat heredoc 方式創(chuàng)建(避免 CRLF 問題) # 3. 進(jìn)入 lycium 目錄 cd /home/weishuo/lycium_plusplus/lycium # 4. 清理歷史構(gòu)建記錄(可選,但推薦) grep -v 'jna' usr/hpk_build.csv > usr/hpk_build.csv.tmp 2>/dev/null || true mv usr/hpk_build.csv.tmp usr/hpk_build.csv 2>/dev/null || true rm -rf ../thirdparty/jna/jna-5.14.0 rm -rf output/*/jna* # 5. 開始構(gòu)建 ./build.sh jna
4.3 構(gòu)建成功輸出示例
構(gòu)建過程中,lycium 框架會(huì)依次執(zhí)行 prepare()、build()、package()、archive() 四個(gè)階段:
關(guān)鍵步驟說明:
- prepare() 階段
- 復(fù)制源碼到 jna-5.14.0/ 目錄
- 使用 sed 修改 dispatch.c 禁用 JAWT
- 運(yùn)行 ant javah 生成 6 個(gè) JNI 頭文件
- 復(fù)制頭文件到 build/native/ 目錄
- build() 階段
- 配置并交叉編譯 libffi 靜態(tài)庫(使用 aarch64-linux-android)
- 編譯 dispatch.c 和 closures.c
- 鏈接生成 libjnidispatch.so
- package() 階段
- 將 libjnidispatch.so 復(fù)制到 usr/lib/ 目錄
- archive() 階段
- 生成 jna_5.14.0.tar.gz 標(biāo)準(zhǔn)壓縮包
- 生成 jna-native.hnp 鴻蒙安裝包

4.4 驗(yàn)證產(chǎn)物
構(gòu)建成功后,編譯產(chǎn)物會(huì)統(tǒng)一輸出至 output 目錄,包含標(biāo)準(zhǔn) tar 壓縮包 與鴻蒙專用 hnp 格式包
查看產(chǎn)物文件:
# 1. 查看 output 目錄 cd /home/weishuo/lycium_plusplus/lycium/output/arm64-v8a ls -lh
驗(yàn)證 tar.gz 內(nèi)容:
# 查看壓縮包內(nèi)容 tar tzf jna_5.14.0.tar.gz

五、鴻蒙 PC 真機(jī)驗(yàn)證:JNA
鴻蒙PC 環(huán)境下完成 JNA 動(dòng)態(tài)庫編譯后,需要將 libjnidispatch.so 部署到設(shè)備并做全面校驗(yàn),確保其格式、架構(gòu)、依賴和導(dǎo)出符號(hào)均滿足運(yùn)行要求。下面分步介紹驗(yàn)證流程及所用命令,并提供一個(gè)自動(dòng)化驗(yàn)證腳本
5.1 部署庫文件:解壓與自簽名
首先將編譯產(chǎn)物放入設(shè)備,解壓歸檔文件,為 .so 文件添加鴻蒙系統(tǒng)所需的簽名和執(zhí)行權(quán)限。
# 查看當(dāng)前目錄文件(確認(rèn)壓縮包存在) ls # 靜默解壓 jna 壓縮包(無警告輸出) tar -zxf jna_5.14.0.tar.gz 2>/dev/null # 進(jìn)入庫文件目錄 cd usr/lib # 鴻蒙系統(tǒng)二進(jìn)制文件自簽名 binary-sign-tool sign -inFile libjnidispatch.so -outFile libjnidispatch.so -selfSign "1" # 添加可執(zhí)行權(quán)限(必須,否則系統(tǒng)無法加載) chmod +x libjnidispatch.so # 驗(yàn)證最終文件狀態(tài) ls -l

說明
- tar -zxf … 2>/dev/null:靜默解壓,避免輸出干擾
- binary-sign-tool sign -selfSign “1”:為動(dòng)態(tài)庫添加 鴻蒙PC 自簽名,系統(tǒng)在加載時(shí)會(huì)校驗(yàn)簽名,未簽名文件會(huì)被拒絕
- chmod +x:確保運(yùn)行時(shí)加載器能夠映射并執(zhí)行該文件
5.2 基礎(chǔ)信息校驗(yàn):文件類型與動(dòng)態(tài)依賴
確認(rèn)庫文件是合法的 ELF 動(dòng)態(tài)庫,并檢查其依賴關(guān)系,排除對(duì)圖形庫(如 X11)的非預(yù)期依賴。
# 查看 libjnidispatch.so 的文件類型 # 輸出顯示它是 aarch64 架構(gòu)的 ELF 動(dòng)態(tài)庫,格式本身沒問題 file libjnidispatch.so # 讀取 ELF 文件的動(dòng)態(tài)節(jié)信息 # 這里可以看到它只依賴了 libc.so,SONAME 是 ../build/native/libjnidispatch.so readelf -d libjnidispatch.so

分析
- file 輸出確認(rèn)該文件為 64?bit aarch64 ELF 共享對(duì)象,格式正確,無損壞。
- readelf -d 顯示的 NEEDED 列表僅包含 libc.so,說明該庫除了標(biāo)準(zhǔn) C 庫外沒有其他運(yùn)行時(shí)依賴(如 libm、libdl 等),在 鴻蒙PC 環(huán)境下加載風(fēng)險(xiǎn)極低。
- SONAME 字段為 …/build/native/libjnidispatch.so,雖帶相對(duì)路徑,但不影響 Native.loadLibrary() 找到同名文件,屬于正常。
5.3 導(dǎo)出符號(hào)檢查:確認(rèn) JNI 接口完整
JNA 通過 JNI 調(diào)用本地方法,因此必須確保 libjnidispatch.so 正確導(dǎo)出了所有 Java_ 開頭的橋接函數(shù)。
# nm 命令:列出目標(biāo)文件/庫的符號(hào)表 # -D 參數(shù):只列出動(dòng)態(tài)符號(hào)(即對(duì)外導(dǎo)出、運(yùn)行時(shí)可見的符號(hào)) nm -D libjnidispatch.so | grep "T Java_" # | grep "T Java_":過濾出類型為 T(text,即代碼段)且以 Java_ 開頭的符號(hào) # 這些符號(hào)是 JNA 橋接層的核心本地方法,Java 側(cè)要調(diào)用它們,必須在 .so 里存在且導(dǎo)出

說明
- nm -D 僅顯示動(dòng)態(tài)符號(hào)表,T 表示全局代碼符號(hào)
- 過濾出的 Java_com_sun_jna_* 均為 JNA 的核心本地接口,全部以 T 形式存在,說明導(dǎo)出表完整,Java 層調(diào)用不會(huì)因符號(hào)丟失而失敗。
5.4 架構(gòu)匹配確認(rèn)
通過 ELF 文件頭再次驗(yàn)證目標(biāo)架構(gòu),確保庫與設(shè)備 CPU 完全匹配。
# 讀取 ELF 文件頭信息,過濾出 Class 和 Machine 字段 # -h 表示讀取文件頭(Header),grep 用來篩選關(guān)鍵信息 readelf -h libjnidispatch.so | grep "Class\|Machine"

輸出解釋
- Class: ELF64:說明該庫為 64 位格式,只能運(yùn)行在 64 位系統(tǒng)上,無法兼容 32 位環(huán)境。
- Machine: AArch64:表示目標(biāo)指令集是 ARM64,與鴻蒙 PC 設(shè)備的 aarch64 架構(gòu)一致,不存在交叉編譯錯(cuò)誤。
5.5 自動(dòng)化驗(yàn)證腳本
為了快速完成上述檢查并生成測(cè)試代碼,可以編寫一個(gè)驗(yàn)證腳本 verify_jna.sh,它會(huì)依次檢驗(yàn)環(huán)境、格式、依賴、導(dǎo)出符號(hào)和 Java 運(yùn)行時(shí),并在條件滿足時(shí)幫助編譯、運(yùn)行一個(gè)簡(jiǎn)單的 JNA 測(cè)試程序。
腳本內(nèi)容
#!/bin/sh
# JNA OpenHarmony 真機(jī)驗(yàn)證腳本
# 使用方法:./verify_jna.sh
# 注意:使用 /bin/sh 確保鴻蒙 PC 兼容性
echo "============================================================"
echo "JNA OpenHarmony 真機(jī)驗(yàn)證"
echo "============================================================"
# 1. 檢查當(dāng)前目錄
echo ""
echo "[1/6] 檢查當(dāng)前環(huán)境..."
CURRENT_DIR=$(pwd)
echo "當(dāng)前目錄: $CURRENT_DIR"
# 檢查 libjnidispatch.so 是否存在
if [ -f "libjnidispatch.so" ]; then
echo "? 找到 libjnidispatch.so"
else
echo "? 未找到 libjnidispatch.so"
echo " 請(qǐng)?jiān)?libjnidispatch.so 所在目錄執(zhí)行此腳本"
exit 1
fi
# 2. 檢查 ELF 格式
echo ""
echo "[2/6] 檢查庫文件格式..."
FILE_OUTPUT=$(file libjnidispatch.so)
echo "$FILE_OUTPUT"
# 檢查是否為 ELF 格式
echo "$FILE_OUTPUT" | grep -q "ELF"
if [ $? -eq 0 ]; then
echo "? ELF 格式正確"
else
echo "? 不是 ELF 格式"
exit 1
fi
# 檢查是否為 64 位
echo "$FILE_OUTPUT" | grep -q "64-bit"
if [ $? -eq 0 ]; then
echo "? 64 位庫"
else
echo "? 不是 64 位庫"
exit 1
fi
# 檢查是否為 ARM64
echo "$FILE_OUTPUT" | grep -q "arm64\|aarch64\|AArch64"
if [ $? -eq 0 ]; then
echo "? ARM64 架構(gòu)"
else
echo "?? 架構(gòu)可能不匹配(期望 ARM64)"
fi
# 3. 檢查動(dòng)態(tài)依賴
echo ""
echo "[3/6] 檢查動(dòng)態(tài)庫依賴..."
echo ""
readelf -d libjnidispatch.so | grep "NEEDED"
# 檢查是否依賴 libc
readelf -d libjnidispatch.so | grep -q "libc.so"
if [ $? -eq 0 ]; then
echo ""
echo "? 依賴 libc.so(正常)"
fi
# 檢查是否有 X11 依賴(不應(yīng)該有)
readelf -d libjnidispatch.so | grep -q "X11\|libX"
if [ $? -eq 0 ]; then
echo "? 發(fā)現(xiàn) X11 依賴(JAWT 禁用失敗)"
exit 1
else
echo "? 無 X11 依賴(JAWT 已禁用)"
fi
# 4. 檢查 JNI 導(dǎo)出符號(hào)
echo ""
echo "[4/6] 檢查 JNI 導(dǎo)出符號(hào)..."
JNI_COUNT=$(nm -D libjnidispatch.so 2>/dev/null | grep "T Java_" | wc -l)
if [ "$JNI_COUNT" -gt 0 ]; then
echo "? 發(fā)現(xiàn) $JNI_COUNT 個(gè) JNI 導(dǎo)出符號(hào)"
echo ""
echo "前 10 個(gè) JNI 函數(shù):"
nm -D libjnidispatch.so | grep "T Java_" | head -10
else
echo "?? 未發(fā)現(xiàn) JNI 導(dǎo)出符號(hào)(可能 nm 命令不可用)"
echo " 嘗試使用 readelf 檢查..."
readelf -s libjnidispatch.so | grep "Java_" | head -10
fi
# 5. 檢查架構(gòu)詳情
echo ""
echo "[5/6] 檢查架構(gòu)詳情..."
readelf -h libjnidispatch.so 2>/dev/null | grep "Class\|Machine"
# 6. 檢查 Java 環(huán)境
echo ""
echo "[6/6] 檢查 Java 環(huán)境..."
if command -v java >/dev/null 2>&1; then
JAVA_VERSION=$(java -version 2>&1 | head -1)
echo "? Java 已安裝: $JAVA_VERSION"
# 檢查是否有 JNA jar 包
echo ""
echo "檢查 JNA jar 包..."
JNA_JAR=$(find . -name "jna-*.jar" -type f | head -1)
if [ -n "$JNA_JAR" ]; then
echo "? 找到 JNA jar 包: $JNA_JAR"
# 創(chuàng)建測(cè)試程序
echo ""
echo "創(chuàng)建測(cè)試程序..."
cat > JNATest.java << 'JAVA_EOF'
import com.sun.jna.Library;
import com.sun.jna.Native;
public class JNATest {
public interface CLibrary extends Library {
CLibrary INSTANCE = Native.load("c", CLibrary.class);
int printf(String format, Object... args);
}
public static void main(String[] args) {
System.out.println("========================================");
System.out.println("JNA OpenHarmony 驗(yàn)證測(cè)試");
System.out.println("========================================");
try {
System.out.println("\n[測(cè)試] 調(diào)用 libc.printf");
CLibrary.INSTANCE.printf(" Hello from JNA on OpenHarmony!\n");
System.out.println("\n? JNA 工作正常!");
System.out.println("========================================");
} catch (UnsatisfiedLinkError e) {
System.err.println("\n? 本地庫加載失敗: " + e.getMessage());
System.err.println("請(qǐng)?jiān)O(shè)置 LD_LIBRARY_PATH:");
System.err.println(" export LD_LIBRARY_PATH=$(pwd):$LD_LIBRARY_PATH");
System.exit(1);
} catch (Exception e) {
System.err.println("\n? 測(cè)試失敗: " + e.getMessage());
e.printStackTrace();
System.exit(1);
}
}
}
JAVA_EOF
echo "? 測(cè)試程序創(chuàng)建成功"
echo ""
echo "============================================================"
echo "? 所有檢查通過!運(yùn)行測(cè)試:"
echo "============================================================"
echo ""
echo " export LD_LIBRARY_PATH=$(pwd):\$LD_LIBRARY_PATH"
echo " javac -cp $JNA_JAR JNATest.java"
echo " java -cp $JNA_JAR:. JNATest"
echo ""
else
echo "?? 未找到 JNA jar 包"
echo " 請(qǐng)下載: https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar"
echo ""
echo " 下載后運(yùn)行:"
echo " export LD_LIBRARY_PATH=$(pwd):\$LD_LIBRARY_PATH"
echo " javac -cp jna-5.14.0.jar JNATest.java"
echo " java -cp jna-5.14.0.jar:. JNATest"
fi
else
echo "?? Java 未安裝或不在 PATH 中"
echo " 需要 Java 運(yùn)行時(shí)才能測(cè)試 JNA"
fi
# 最終總結(jié)
echo ""
echo "============================================================"
echo "驗(yàn)證總結(jié)"
echo "============================================================"
echo ""
echo "? libjnidispatch.so 文件格式正確"
echo "? ARM64 架構(gòu)匹配"
echo "? 無 X11 依賴(JAWT 已禁用)"
echo "? 依賴干凈(僅 libc.so)"
echo ""
if [ "$JNI_COUNT" -gt 0 ]; then
echo "? JNI 導(dǎo)出符號(hào)正常($JNI_COUNT 個(gè))"
fi
echo ""
echo "結(jié)論:JNA 本地庫已成功適配到 OpenHarmony!"
echo "============================================================"


腳本分析
該腳本分為六個(gè)檢查步驟:
- 環(huán)境檢查 —— 確認(rèn)當(dāng)前目錄包含 libjnidispatch.so
- ELF** 格式檢驗(yàn)** —— 利用 file 命令驗(yàn)證格式是否為 64 位 ARM64 ELF,防止文件損壞或架構(gòu)錯(cuò)誤
- 動(dòng)態(tài)依賴掃描 —— 通過 readelf -d 列出 NEEDED 項(xiàng),確保沒有 X11 等不該出現(xiàn)的依賴,僅依賴 libc.so。
- JNI 符號(hào)導(dǎo)出驗(yàn)證 —— 統(tǒng)計(jì)并展示 Java_ 開頭的全局符號(hào),確認(rèn)橋接層接口完整
- 架構(gòu)詳情確認(rèn) —— 再次用 readelf -h 展示 ELF 頭中的 Class 和 Machine 字段,增強(qiáng)可讀性
- Java 環(huán)境與測(cè)試 —— 檢測(cè) Java 是否可用,查找 JNA JAR 包,生成并編譯 JNATest.java,給出運(yùn)行指令。若庫文件和 Jar 包就緒,可直接編譯運(yùn)行一個(gè)調(diào)用 libc.printf 的簡(jiǎn)單示例來最終驗(yàn)證 JNA 是否能正常加載并工作
通過這套自動(dòng)化和人工結(jié)合的校驗(yàn)流程,可以確定 libjnidispatch.so 已成功適配至 鴻蒙PC 平臺(tái),格式完整、依賴純凈、接口齊全,能夠?yàn)樯蠈?Java 應(yīng)用提供可靠的 JNA 本地調(diào)用支持
六、常見問題與解決方案(FAQ)
6.1 編譯錯(cuò)誤類
Q1:構(gòu)建時(shí)報(bào)錯(cuò) Hunk #1 FAILED at 113 或 malformed patch at line 20?
A:這是 patch 文件上下文行號(hào)不匹配導(dǎo)致的。原因可能是:
- JNA 版本不同,dispatch.c 的實(shí)際行號(hào)有差異
- 文件有 CRLF 換行符,導(dǎo)致 patch 解析失敗
- 之前的修改已經(jīng)改變了文件內(nèi)容
解決方案:使用 sed 命令代替 patch 文件,更加靈活可靠。
# 在 HPKBUILD 的 prepare() 函數(shù)中使用 sed # 1. 添加 NO_JAWT 定義 sed -i '/#include <wchar.h>/a\ \ /* OpenHarmony: Disable JAWT to avoid X11 dependency */\ #ifndef NO_JAWT\ #define NO_JAWT 1\ #endif' native/dispatch.c # 2. 替換 jni.h 包含 sed -i 's/^#include <jni.h>$/#include "com_sun_jna_Native.h"\n#include "com_sun_jna_Function.h"/' native/dispatch.c # 3. 修改 JAWT 條件編譯 sed -i 's/^#ifndef NO_JAWT$/#ifdef DISABLE_JAWT_COMPLETELY/' native/dispatch.c
Q2:編譯時(shí)報(bào)錯(cuò) fatal error: ‘X11/Xlib.h’ file not found?
A:原因是 JDK 的 jawt_md.h 硬編碼需要 X11 頭文件,但 OpenHarmony 是無頭系統(tǒng)(headless),不需要圖形界面。HPKBUILD 中已包含自動(dòng)修復(fù)邏輯,通過 sed 修改 dispatch.c 禁用 JAWT。
# HPKBUILD 的 prepare() 函數(shù)中已包含自動(dòng)修復(fù)代碼 sed -i '/#include <wchar.h>/a\ \ /* OpenHarmony: Disable JAWT to avoid X11 dependency */\ #ifndef NO_JAWT\ #define NO_JAWT 1\ #endif' native/dispatch.c sed -i 's/^#ifndef NO_JAWT$/#ifdef DISABLE_JAWT_COMPLETELY/' native/dispatch.c
原理說明:
- dispatch.c 第 114 行有 #include <jni.h>
- 在 Linux 上,jni.h 會(huì)包含 jawt_md.h
- jawt_md.h 第 29 行有 #include <X11/Xlib.h>
- 通過定義 NO_JAWT 宏并修改條件編譯,跳過 JAWT 相關(guān)代碼
Q3:編譯時(shí)報(bào)錯(cuò) configure: error: cannot run C compiled programs. If you meant to cross compile, use ‘–host’?
A:原因是 libffi 的 configure 腳本未識(shí)別交叉編譯環(huán)境。需要在 make 命令中添加 HOST_CONFIG 和 FFI_CONFIG 參數(shù),使用 aarch64-linux-android 而不是 aarch64-linux-ohos(老版本 libffi 不認(rèn)識(shí) ohos)。
# HPKBUILD 的 build() 函數(shù)中已配置
export FFI_CONFIG="--enable-static --disable-shared --with-pic=yes --host=aarch64-linux-android"
export HOST_CONFIG="--host=aarch64-linux-android"
# 傳遞給 make 命令
make \
HOST_CONFIG="--host=aarch64-linux-android" \
FFI_CONFIG="--enable-static --disable-shared --with-pic=yes --host=aarch64-linux-android"
為什么使用 android:
- 老版本 libffi 的 config.sub 不認(rèn)識(shí) ohos 目標(biāo)
- aarch64-linux-android 與 鴻蒙PC 兼容(都是 musl libc)
- 這是經(jīng)過驗(yàn)證的可行方案
Q4:編譯時(shí)報(bào)錯(cuò) libtool: error: cannot build a shared library?
A:原因是 LDFLAGS 環(huán)境變量中包含了 -shared 標(biāo)志,傳遞給了 libffi 的構(gòu)建,但 libffi 配置為靜態(tài)庫(–disable-shared),產(chǎn)生沖突。
解決方案:不設(shè)置全局 LDFLAGS 環(huán)境變量,讓 Makefile 使用自己的默認(rèn)值。
# ? 錯(cuò)誤做法:設(shè)置 LDFLAGS 會(huì)影響 libffi
export LDFLAGS="--target=aarch64-linux-ohos --sysroot=${OHOS_SDK}/native/sysroot -shared"
# ? 正確做法:不設(shè)置 LDFLAGS,讓 Makefile 處理
# Makefile 中已有:LDFLAGS=-o $@ -shared (用于鏈接 libjnidispatch.so)
Q5:編譯時(shí)報(bào)錯(cuò) fatal error: ‘com_sun_jna_Function.h’ file not found?
A:原因是 JNI 頭文件未生成。需要安裝 JDK 11 和 Ant,并在 prepare() 階段運(yùn)行 ant javah 生成頭文件。
# 1. 安裝 JDK 11 和 Ant sudo apt install -y openjdk-11-jdk sudo apt install -y ant # 2. 設(shè)置 JAVA_HOME export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64 # 3. 生成 JNI 頭文件 cd jna-5.14.0 ant javah # 4. 復(fù)制到頭文件目錄(Makefile 期望的位置) mkdir -p build/native cp -f build/headers/*.h build/native/
注意:make clean 會(huì)刪除 build/native 目錄,需要在 make clean 后重新復(fù)制頭文件。
Q6:編譯時(shí)報(bào)錯(cuò) fatal error: ‘jni.h’ file not found?
A:原因是 CFLAGS 中缺少 JDK include 路徑。需要添加 JDK 的 include 和 include/linux 目錄。
export JAVA_HOME=/usr/lib/jvm/java-11-openjdk-amd64
export CFLAGS="... -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux"
完整 CFLAGS 示例:
export CFLAGS="--target=aarch64-linux-ohos \
--sysroot=${OHOS_SDK}/native/sysroot \
-O2 -fPIC -fno-strict-aliasing \
-I../build/native/libffi/include \
-I../build/native \
-I../build/headers \
-I${JAVA_HOME}/include \
-I${JAVA_HOME}/include/linux"
Q7:編譯時(shí)報(bào)錯(cuò) ld.lld: error: undefined symbol: main?
A:原因是鏈接共享庫時(shí)缺少 -shared 標(biāo)志,鏈接器誤以為在構(gòu)建可執(zhí)行文件。這是因?yàn)?LDFLAGS 環(huán)境變量覆蓋了 Makefile 的默認(rèn)值。
解決方案:刪除 LDFLAGS 環(huán)境變量,讓 Makefile 使用默認(rèn)配置。
Q8:構(gòu)建時(shí)報(bào)錯(cuò) $‘\r’: command not found?**
A:這是 Windows 換行符(CRLF)導(dǎo)致的問題。在 WSL + Windows 共享目錄環(huán)境下,使用 Windows 編輯器創(chuàng)建的文件會(huì)自動(dòng)帶有 CRLF 換行符。解決方案是使用 Python 腳本修復(fù)。
# 創(chuàng)建 fix_crlf.py 腳本
python3 << 'EOF'
import os
files = [
"/home/weishuo/lycium_plusplus/thirdparty/jna/HPKBUILD",
"/home/weishuo/lycium_plusplus/thirdparty/jna/HPKCHECK"
]
for filepath in files:
with open(filepath, "rb") as f:
content = f.read()
content = content.replace(b"\r\n", b"\n").replace(b"\r", b"\n")
with open(filepath, "wb") as f:
f.write(content)
print(f"Fixed: {filepath}")
print("All files fixed!")
EOF
6.2 環(huán)境問題類
Q9:執(zhí)行 ./build.sh jna 后,日志顯示 ALL JOBS DONE!!! 但 output 目錄為空?
A:這通常是因?yàn)?hpk_build.csv 中已有該包的構(gòu)建記錄,導(dǎo)致構(gòu)建被跳過。需要手動(dòng)清理構(gòu)建記錄和緩存。
cd lycium_plusplus/lycium grep -v 'jna' usr/hpk_build.csv > usr/hpk_build.csv.tmp mv usr/hpk_build.csv.tmp usr/hpk_build.csv rm -rf ../thirdparty/jna/jna-5.14.0 rm -rf output/*/jna* ./build.sh jna
Q10:編譯時(shí)報(bào)錯(cuò) fatal error: ‘ffi.h’ file not found?
A:原因是 CFLAGS 中缺少 libffi 頭文件路徑。需要添加 libffi 編譯后的 include 目錄。
export CFLAGS="... -I../build/native/libffi/include"
完整構(gòu)建路徑說明:
- libffi 頭文件:…/build/native/libffi/include/ffi.h
- JNI 頭文件:…/build/native/com_sun_jna_*.h
- JDK 頭文件:${JAVA_HOME}/include/jni.h
Q11:如何驗(yàn)證生成的庫文件是否正確?
A:使用 file 和 nm 命令檢查 ELF 文件格式和導(dǎo)出符號(hào)。
# 1. 檢查 ELF 格式 file ~/lycium_plusplus/lycium/usr/jna/arm64-v8a/usr/lib/libjnidispatch.so # 正確輸出: # ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), # dynamically linked, stripped # 2. 檢查 JNI 導(dǎo)出符號(hào) nm -D ~/lycium_plusplus/lycium/usr/jna/arm64-v8a/usr/lib/libjnidispatch.so | grep "T Java_" # 應(yīng)該看到類似輸出: # 000000000000c454 T Java_com_sun_jna_Native__1getDirectBufferPointer # 000000000000b42c T Java_com_sun_jna_Native__1getPointer # 000000000000ad54 T Java_com_sun_jna_Native_close # ...
6.3 使用與驗(yàn)證類
Q13:如何在鴻蒙 PC 上使用 JNA?
A:JNA 是純運(yùn)行時(shí) Java 庫,Java 端調(diào)用完全不需要 C 頭文件。只需提供 libjnidispatch.so 文件,配合 jna.jar 使用。
// Java 代碼,不需要任何 C 頭文件
import com.sun.jna.Library;
import com.sun.jna.Native;
public class MyProgram {
public interface CLibrary extends Library {
CLibrary INSTANCE = Native.load("c", CLibrary.class);
int printf(String format, Object... args);
}
public static void main(String[] args) {
CLibrary.INSTANCE.printf("Hello from JNA on OpenHarmony!\n");
}
}
# 運(yùn)行 Java 程序 export LD_LIBRARY_PATH=./usr/lib:$LD_LIBRARY_PATH javac -cp jna-5.14.0.jar MyProgram.java java -cp jna-5.14.0.jar:. MyProgram
Q14:JNA 需要安裝頭文件嗎?
A:不需要。JNA 是純運(yùn)行時(shí) Java 庫,其 Java 端調(diào)用完全不需要 C 頭文件(如 dispatch.h、protect.h、ffi.h)。libjnidispatch.so 已靜態(tài)鏈接 libffi,所有底層調(diào)度由 JNA jar 包自動(dòng)完成。用戶只需提供 .so 文件,無需打包或安裝任何 .h 頭文件。
Q15:如何系統(tǒng)排查構(gòu)建錯(cuò)誤?
A:按以下步驟排查:
# 1. 查看完整日志 ./build.sh jna 2>&1 | tee build.log # 2. 確認(rèn)失敗階段:prepare / build / package / archive # 3. 檢查 prepare() ls -la thirdparty/jna/jna-5.14.0/ cat log/jna-javah.log # 4. 檢查 build() cat log/jna-build.log # 5. 檢查 package() - 確認(rèn) destdir 路徑和庫文件是否存在 # 6. 檢查 archive() - 確認(rèn)打包路徑是否正確
Q16:為什么選擇本地源碼而非 Git 下載?
A:本地源碼模式更適合快速迭代開發(fā),避免每次構(gòu)建都重新下載。JNA 包含 native 目錄和 Java 代碼,本地源碼可以確保版本一致性。對(duì)于已經(jīng)驗(yàn)證過的穩(wěn)定版本,可以改為從 GitHub 下載。
# 改為從 GitHub 下載(修改 HPKBUILD)
source="https://github.com/java-native-access/jna/archive/refs/tags/${pkgver}.tar.gz"
autounpack=true
downloadpackage=true
Q17:測(cè)試時(shí)遇到 UnsatisfiedLinkError 怎么辦?
A:這通常是庫路徑設(shè)置問題。按以下步驟排查:
# 1. 檢查 LD_LIBRARY_PATH echo $LD_LIBRARY_PATH # 應(yīng)該包含 ./usr/lib # 2. 檢查庫文件是否存在 ls -l ./usr/lib/libjnidispatch.so # 3. 檢查架構(gòu)是否匹配 file ./usr/lib/libjnidispatch.so # 必須是 ARM64 # 4. 檢查依賴庫 ldd ./usr/lib/libjnidispatch.so # 確保所有依賴都找到
Q18:可以在其他架構(gòu)(如 armeabi-v7a)上構(gòu)建嗎?
A:可以。修改 HPKBUILD 中的 archs 變量,并調(diào)整工具鏈:
# 修改 HPKBUILD
archs=("armeabi-v7a")
# 修改工具鏈(在 build() 函數(shù)中)
export CC="${OHOS_SDK}/native/llvm/bin/armv7a-linux-ohos-clang"
export CFLAGS="--target=armv7a-linux-ohos ..."
七、技術(shù)總結(jié)
本次將 JNA 本地庫適配至 鴻蒙PC 平臺(tái),完整驗(yàn)證了 Java JNI 本地庫在鴻蒙環(huán)境下的交叉編譯、構(gòu)建打包與依賴處理流程,形成了可復(fù)用的適配范式。通過規(guī)范 HPKBUILD 配置、禁用 JAWT 避免 X11 依賴、使用 android 兼容方案交叉編譯 libffi、自動(dòng)生成 JNI 頭文件等關(guān)鍵處理,實(shí)現(xiàn)了庫正常編譯運(yùn)行與輕量化部署,相關(guān)思路可廣泛遷移至各類 Java JNI 本地庫的鴻蒙移植工作。
- 建立了 Makefile 類項(xiàng)目標(biāo)準(zhǔn)化的 HPKBUILD 適配模板,明確交叉編譯與 JNI 頭文件生成要點(diǎn)
- 解決 X11 圖形依賴、libffi 交叉編譯、JNI 頭文件自動(dòng)生成、共享庫鏈接配置等常見適配問題
- 通過純運(yùn)行時(shí)庫設(shè)計(jì)實(shí)現(xiàn)零頭文件交付,提升在鴻蒙設(shè)備上的部署效率
- 適配方案具備通用性,可直接用于其他 Java JNI 本地庫(如 JNR、JNIWrapper)移植
- 為后續(xù)多架構(gòu)擴(kuò)展、Java 應(yīng)用集成、自動(dòng)化適配工具開發(fā)奠定基礎(chǔ)
八、結(jié)語
本次實(shí)踐成功將 JNA 本地庫移植至 鴻蒙PC 平臺(tái),充分體現(xiàn)了 lycium_plusplus 框架對(duì) Java JNI 項(xiàng)目交叉編譯的支撐能力。通過合理運(yùn)用 HPKBUILD 構(gòu)建流程、禁用 JAWT 避免 X11 依賴、使用 android 兼容方案、規(guī)范 Make 編譯配置、自動(dòng)生成 JNI 頭文件等關(guān)鍵措施,有效解決了適配中的兼容性與構(gòu)建問題,為同類開源 Java JNI 本地庫遷移至鴻蒙生態(tài)提供了可復(fù)用的思路與實(shí)踐參考,也助力開源鴻蒙原生工具生態(tài)的完善與發(fā)展。
提示:本文基于 JNA 5.14.0 版本進(jìn)行適配。不同版本的依賴和構(gòu)建腳本可能有所差異,建議在適配前先熟悉目標(biāo)項(xiàng)目的 Makefile 和構(gòu)建配置文件。JNA 是純運(yùn)行時(shí)庫,Java 端使用不需要任何 C 頭文件,只需提供 libjnidispatch.so 配合 jna.jar 即可。
到此這篇關(guān)于[鴻蒙PC三方庫適配]Java本地訪問庫JNA適配到鴻蒙PC平臺(tái)的文章就介紹到這了,更多相關(guān)JNA本地庫適配到鴻蒙PC平臺(tái)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Failed?to?load?ApplicationContext異常的解決思路
這篇文章主要介紹了基于Failed?to?load?ApplicationContext異常的解決思路,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01
SpringBoot詳解如何實(shí)現(xiàn)讀寫分離
當(dāng)響應(yīng)的瓶頸在數(shù)據(jù)庫的時(shí)候,就要考慮數(shù)據(jù)庫的讀寫分離,當(dāng)然還可以分庫分表,那是單表數(shù)據(jù)量特別大,當(dāng)單表數(shù)據(jù)量不是特別大,但是請(qǐng)求量比較大的時(shí)候,就要考慮讀寫分離了.具體的話,還是要看自己的業(yè)務(wù)...如果還是很慢,那就要分庫分表了...我們這篇就簡(jiǎn)單講一下讀寫分離2022-05-05
Spring Security基于json登錄實(shí)現(xiàn)過程詳解
這篇文章主要介紹了Spring Security基于json登錄實(shí)現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
springboot配置多數(shù)據(jù)源(靜態(tài)和動(dòng)態(tài)數(shù)據(jù)源)
在開發(fā)過程中,很多時(shí)候都會(huì)有垮數(shù)據(jù)庫操作數(shù)據(jù)的情況,需要同時(shí)配置多套數(shù)據(jù)源,本文主要介紹了springboot配置多數(shù)據(jù)源(靜態(tài)和動(dòng)態(tài)數(shù)據(jù)源),感興趣的可以了解一下2023-09-09
JAVA實(shí)現(xiàn)經(jīng)典游戲坦克大戰(zhàn)的示例代碼
小時(shí)候大家都玩過坦克大戰(zhàn)吧,熟悉的旋律和豐富的關(guān)卡陪伴了我們一整個(gè)寒暑假。本文將通過Java+Swing實(shí)現(xiàn)這一經(jīng)典游戲,感興趣的可以學(xué)習(xí)一下2022-01-01
Java中冒泡排序的原生實(shí)現(xiàn)方法(正序與逆序)
這篇文章主要給大家介紹了關(guān)于Java中冒泡排序的原生實(shí)現(xiàn)方法(正序與逆序)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11

