最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

[鴻蒙PC三方庫適配]Java本地訪問庫JNA適配到鴻蒙PC平臺

 更新時間:2026年06月27日 13:46:39   作者:程序山海  
本文詳細介紹了如何將 Java Native Access (JNA) 本地庫適配到 鴻蒙PC 平臺,文章將系統(tǒng)性地講解如何利用 lycium_plusplus 構(gòu)建框架,處理 Java JNI 本地庫在鴻蒙環(huán)境下的交叉編譯流程

本文是軟件鴻蒙化遷移實踐系列文章之一,專注于 Java JNI 本地庫的鴻蒙適配,為開發(fā)者提供完整的遷移指南。

歡迎加入開源鴻蒙PC社區(qū):https://harmonypc.csdn.net/

歡迎在PC社區(qū)平臺申請新建項目:https://atomgit.com/OpenHarmonyPCDeveloper

AtomGit 倉庫地址:https://atomgit.com/OpenHarmonyPCDeveloper/ohos_jna_pc

項目信息說明

項目說明
名稱JNA (Java Native Access)
開源協(xié)議LGPL-2.1 / Apache-2.0
源碼版本5.14.0
目標平臺鴻蒙 PC
依賴項JDK 11, Ant, libffi
操作系統(tǒng)平臺WSL Ubuntu 24.04

一、背景介紹

1.0 功能與效果

JNA 本地庫在本實踐中的預期能力如下:

功能:為 Java 程序提供訪問本地共享庫的能力,無需編寫 JNI 代碼。JNA 通過 libjnidispatch.so 實現(xiàn) Java 與本地 C/C++ 庫之間的自動調(diào)度。

效果:在鴻蒙 PC 上提供與標準 Linux 環(huán)境相近的 JNA 使用體驗,便于在鴻蒙 Java 應用開發(fā)中實現(xiàn)本地庫調(diào)用、系統(tǒng) API 訪問、硬件接口交互等場景的本地調(diào)度功能。

1.1 什么是 鴻蒙PC HNP 生態(tài)

HNP(Harmony Native Package)是 鴻蒙PC 的原生包格式,lycium 是增強型構(gòu)建框架,支持自動下載源碼、交叉編譯(arm64-v8a、armeabi-v7a)、一鍵生成 HNP 包以及開源聲明聚合。C/C++ 原生庫的適配是鴻蒙系統(tǒng)生態(tài)建設(shè)中的重要一環(huán)。

為什么 JNA 需要 C/C++ 構(gòu)建?

雖然 JNA 是 Java 庫,但它包含一個核心的本地調(diào)度庫 libjnidispatch.so(C 代碼實現(xiàn))。這個 so 文件負責:

  • Java 與本地 C/C++ 庫之間的自動調(diào)度
  • 數(shù)據(jù)類型轉(zhuǎn)換和內(nèi)存管理
  • 函數(shù)調(diào)用的底層實現(xiàn)

因此,我們需要使用 lycium_plusplus 框架交叉編譯這個 C 語言本地庫。

1.2 為什么適配 JNI 本地庫會有難度

常見挑戰(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 不認識 ohos 目標,需要使用 android 兼容方案。
  • JNI 頭文件生成:需要 JDK 11 和 Ant 工具,通過 ant javah 自動生成 JNI 頭文件。
  • 共享庫****鏈接配置:Makefile 默認使用 -shared 標志,但環(huán)境變量 LDFLAGS 會覆蓋 Makefile 的默認值,需要特殊處理。
  • Windows 元數(shù)據(jù)干擾:從 Windows 環(huán)境復制的源碼可能包含 :Zone.Identifier 等區(qū)域標識文件,需要在打包前清理。
  • 構(gòu)建緩存問題:lycium 使用 hpk_build.csv 跟蹤構(gòu)建狀態(tài),需要正確清理緩存才能重新構(gòu)建。

1.3 JNA 簡介

JNA (Java Native Access) 是一款由 Sun Microsystems(現(xiàn) Oracle)開發(fā)的 Java 庫,提供訪問本地共享庫的能力而無需編寫 JNI 代碼。其主要特點包括:

  • 零 JNI 代碼:Java 開發(fā)者無需編寫 C/C++ 代碼即可調(diào)用本地庫。
  • 自動類型映射:自動處理 Java 類型與 C 類型之間的轉(zhuǎn)換。
  • 跨平臺特性:原生支持 Windows、macOS、Linux,本次適配擴展到 鴻蒙PC 平臺。
  • 廣泛使用:被 NetBeans、Eclipse、IntelliJ IDEA 等知名項目使用。
  • 開源地址
    • GitHub:https://github.com/java-native-access/jna
    • AtomGit:https://atomgit.com/weixin_62765017/ohos_jna.git

二、環(huá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++)
  • 目標架構(gòu):arm64-v8a(AArch64)

2.2 擴展閱讀與參考教程

下方匯總展示了多位老師在鴻蒙 鴻蒙PC 適配方面的高質(zhì)量教程。若在前提準備(環(huán)境、工具鏈、框架)部分還有不清楚的地方,可參考這些文章進一步學習。 以下資源不分先后順序,均具有參考價值。

資源類型描述鏈接
三方庫交叉編譯環(huán)境(Ubuntu)在 Ubuntu 中搭建鴻蒙PC 三方庫交叉編譯構(gòu)建開發(fā)環(huán)境?? 點擊查看
三方庫交叉編譯環(huán)境(macOS)在 macOS 中搭建鴻蒙PC 三方庫交叉編譯開發(fā)環(huán)境?? 點擊查看
基礎(chǔ)環(huán)境搭建Windows 10 上安裝和使用 WSL 2、安裝 Ubuntu 24 詳細指南?? 點擊查看
Mac 移植指南鴻蒙PC命令行適配指南(Mac 版)?? 點擊查看
Win 移植指南鴻蒙PC 生態(tài)三方軟件移植:開發(fā)環(huán)境搭建及三方庫移植指南?? 點擊查看
全流程適配指南OpenHarmony Linux 命令行工具適配實戰(zhàn):基于 Cursor × WSL 的 tree 2.2.1 交叉編譯與 HNP 打包全流程指南?? 點擊查看
官方構(gòu)建文檔新腳手架:社區(qū)維護的鴻蒙PC 生態(tài)命令行工具構(gòu)建框架 lycium_plusplus(原 build 倉庫為舊方式,請以本倉庫為準)?? 點擊查看

2.3 配置 鴻蒙PC SDK 環(huán)境變量

在開始之前,需要先配置 鴻蒙PC SDK 路徑。這是所有后續(xù)操作的基礎(chǔ)。

# 配置 鴻蒙PC SDK 環(huán)境變量(請根據(jù)實際路徑修改)
export OHOS_SDK=/home/weishuo/ohos-sdk/linux

# 驗證 SDK 是否存在
ls ${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang
# 應該輸出:/home/weishuo/ohos-sdk/linux/native/llvm/bin/aarch64-linux-ohos-clang

注意:如果 SDK 路徑不同,請修改為你的實際路徑。

2.4 lycium_plusplus 框架

lycium_plusplus 是本次適配工作的核心工具,主要用于統(tǒng)一管理各類第三方庫的構(gòu)建流程,通過規(guī)范編譯、依賴與打包邏輯,實現(xiàn)三方庫在目標平臺上高效、穩(wěn)定地編譯與集成,是整個適配環(huán)節(jié)中保障構(gòu)建一致性與可維護性的關(guān)鍵支撐。

# 克隆 lycium_plusplus 項目
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

# 驗證安裝
java -version
javac -version
ant -version

# 配置 鴻蒙PC SDK 環(huán)境變量
export OHOS_SDK=/home/weishuo/ohos-sdk/linux

三、實戰(zhàn):以 JNA 為例的適配步驟

本章節(jié)將為新人開發(fā)者提供完整的、可復現(xiàn)的適配步驟。我們將從零開始,逐步完成 JNA 本地庫的鴻蒙適配工作。每個步驟都包含詳細的說明、命令示例和注意事項。

3.1 創(chuàng)建項目目錄結(jié)構(gòu)

步驟說明

在 lycium_plusplus 框架中,每個三方庫都需要在 thirdparty/ 目錄下?lián)碛歇毩⒌哪夸?。這個目錄將存放該庫的所有構(gòu)建配置文件(HPKBUILD、hnp.json、README.OpenSource、HPKCHECK 等)。

詳細操作流程

# 1. 進入 lycium_plusplus 項目根目錄
cd /home/weishuo/lycium_plusplus

# 2. 進入 thirdparty 目錄
cd thirdparty

# 3. 創(chuàng)建 jna 目錄
mkdir -p jna

# 4. 進入新創(chuàng)建的目錄
cd jna

# 5. 驗證目錄創(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        ← 將要創(chuàng)建的構(gòu)建腳本
│   │   ├── hnp.json        ← 將要創(chuàng)建的包元數(shù)據(jù)
│   │   ├── README.OpenSource  ← 將要創(chuàng)建的開源聲明
│   │   └── HPKCHECK        ← 將要創(chuàng)建的檢查腳本
│   ├── mediainfo/          ← 其他三方庫示例
│   ├── fuse3/              ← 其他三方庫示例
│   └── ...
├── lycium/
│   ├── build.sh            ← lycium 構(gòu)建入口腳本
│   └── usr/                ← 構(gòu)建產(chǎn)物輸出目錄
└── Projects/
    └── jna/                ← JNA 源碼目錄(需提前準備)

注意事項

  • 目錄名稱必須與 pkgname 變量保持一致(本例中為 jna)
  • 確保 Projects/jna/ 目錄中已經(jīng)有 JNA 的源碼
  • 如果源碼還未準備,需要先下載或克隆源碼到 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)點缺點
patch 文件直觀、易讀依賴行號,版本不同可能失敗
sed 命令靈活、不依賴行號語法稍復雜

本文選擇:使用 sed 命令,直接在 HPKBUILD 的 prepare() 函數(shù)中修改源碼,無需額外創(chuàng)建 patch 文件。

注意事項

  • 這三條 sed 命令會在后面的 HPKBUILD 的 prepare() 函數(shù)中自動執(zhí)行
  • 你不需要手動運行這些命令,只需要理解它們的原理即可

3.3 創(chuàng)建 HPKBUILD 文件(核心構(gòu)建腳本)

什么是 HPKBUILD?

HPKBUILD 是 lycium 框架的核心構(gòu)建腳本,可以理解為一個"構(gòu)建配方"。它告訴 lycium 框架:

  • 這個庫叫什么、什么版本、什么許可證(元信息)
  • 如何準備源碼、如何編譯、如何打包(構(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():準備源碼
# -----------------------------------------------------------------------------
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
        
        # 復制 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è)置編譯標志(注意:不要設(shè)置 LDFLAGS 環(huán)境變量,讓 Makefile 使用自己的默認值)
    # 添加 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 因為老版本 libffi 不認識 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 會刪除 build/native 目錄,需要重新復制 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():驗證構(gòu)建產(chǎn)物
# -----------------------------------------------------------------------------
check() {
    echo "The test must be on an OpenHarmony device!"
}

# -----------------------------------------------------------------------------
# package():打包產(chǎn)物
# -----------------------------------------------------------------------------
package() {
    : ${destdir:=${LYCIUM_ROOT}/usr/${pkgname}/${ARCH}}
    
    # 只復制 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,運行時依賴為空,JNA 無需額外 HNP 依賴包
  • 第 2 部分:prepare () 準備函數(shù):完成源碼復制至編譯目錄,關(guān)閉 JAWT 組件以規(guī)避 X11 依賴,通過 ant javah 指令編譯生成 JNI 頭文件
  • 第 3 部分:build () 構(gòu)建函數(shù):配置 aarch64-linux-ohos-clang 交叉編譯工具鏈,采用安卓主機三元組編譯 libffi 靜態(tài)庫,最終完成 libjnidispatch.so 動態(tài)庫編譯
  • 第 4 部分:package () 打包函數(shù):將編譯產(chǎn)出的 libjnidispatch.so 庫文件,拷貝至系統(tǒng) usr/lib 目錄完成部署
  • 第 5 部分:archive () 歸檔函數(shù):輸出 tar.gz 格式壓縮包,檢測 hnpcli 工具是否存在,按需同步生成 hnp 安裝包

構(gòu)建流程

prepare()   →  準備源碼、修改代碼、生成 JNI 頭文件
    ↓
build()     →  交叉編譯 libffi、編譯 libjnidispatch.so
    ↓
package()   →  安裝 .so 文件到 usr/lib/
    ↓
archive()   →  生成 tar.gz 和 hnp 包

3.4 創(chuàng)建 jna-disable-jawt.patch 補丁文件

什么是 patch 文件?

patch 文件是用于修改源碼的文本文件,記錄了需要修改的文件位置和修改內(nèi)容。在 JNA 適配中,我們需要通過 patch 修改 dispatch.c 來禁用 JAWT 依賴。

為什么需要這個補丁

  1. X11 依賴問題:JDK 的 jawt_md.h 硬編碼包含 #include <X11/Xlib.h>
  2. 鴻蒙PC 無 X11:鴻蒙PC 是無頭系統(tǒng)(headless),沒有圖形界面,不提供 X11 庫
  3. JAWT 非必需:JNA 的核心功能不需要 JAWT,只有需要 Java GUI 組件才需要
  4. 編譯阻斷:不修改會導致編譯錯誤 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

補丁修改說明

  1. 第一處修改:在 #include <jni.h> 之前定義 NO_JAWT 宏
    1. 這個宏會告知 JNA 代碼禁用 JAWT 相關(guān)功能
  2. 第二處修改:將 #include <jni.h> 替換為直接包含 JNI 頭文件
    1. 跳過 jni.h 間接包含 jawt_md.h 的鏈條
    2. 直接包含 com_sun_jna_Native.h 和 com_sun_jna_Function.h
  3. 第三處修改:將 #ifndef NO_JAWT 改為 #ifdef DISABLE_JAWT_COMPLETELY
    1. DISABLE_JAWT_COMPLETELY 宏永遠不定義,所以 JAWT 代碼永遠不會編譯

使用方式

# 在 HPKBUILD 的 prepare() 中應用 patch
cd "$builddir"
patch -p1 < "../jna-disable-jawt.patch"

注意事項

  • patch 文件可能因 JNA 版本不同而需要調(dià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):

  • 這個包叫什么、什么版本、什么許可證
  • 包含哪些文件、安裝到哪些目錄
  • 依賴哪些其他包

創(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:版本號,與 HPKBUILD 中的 pkgver 一致
  • arch:目標架構(gòu),arm64-v8a 表示 64 位 ARM
  • install.lib:要安裝的庫文件列表

3.6 創(chuàng)建 README.OpenSource(開源聲明)

什么是 README.OpenSource?

README.OpenSource 是開源合規(guī)聲明文件,記錄:

  • 使用了哪些開源組件
  • 每個組件的許可證類型
  • 上游源碼地址和版本

這是鴻蒙生態(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 是自動驗證腳本,在構(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)建檢查腳本,用于在編譯前驗證環(huán)境是否滿足構(gòu)建要求,以及在編譯后驗證產(chǎn)物是否正確。它相當于一個"自動化測試腳本",確保構(gòu)建質(zhì)量。

HPKCHECK 的作用

  1. 環(huán)境驗證:檢查編譯環(huán)境是否滿足要求
  2. 產(chǎn)物驗證:驗證生成的庫文件是否存在且格式正確
  3. 架構(gòu)檢查:驗證 ELF 文件格式和目標架構(gòu)
  4. 日志記錄:記錄測試結(jié)果到日志文件

四、編譯流程與完整示例

4.1 環(huán)境準備

配置用于前置環(huán)境檢查與變量設(shè)置,通過指定 鴻蒙PC SDK 路徑、驗證 JDK 和 Ant 環(huán)境,為 lycium_plusplus 構(gòu)建 JNA 三方庫提供基礎(chǔ)運行環(huán)境

# 1. 確保 鴻蒙PC SDK 已安裝
export OHOS_SDK=/home/weishuo/ohos-sdk/linux

# 2. 確保 JDK 11 和 Ant 已安裝
java -version
javac -version
ant -version

# 3. 驗證交叉編譯工具鏈
ls ${OHOS_SDK}/native/llvm/bin/aarch64-linux-ohos-clang
# 應該輸出:/home/weishuo/ohos-sdk/linux/native/llvm/bin/aarch64-linux-ohos-clang

4.2 創(chuàng)建項目結(jié)構(gòu)并執(zhí)行編譯

通過目錄操作、腳本創(chuàng)建、清理緩存、執(zhí)行構(gòu)建指令,完成 lycium_plusplus 中 JNA 三方庫的從零構(gòu)建全流程

# 1. 進入 thirdparty 目錄并創(chuàng)建 jna 目錄
cd /home/weishuo/lycium_plusplus/thirdparty
mkdir -p jna
cd jna

# 2. 創(chuàng)建 4 個核心文件(內(nèi)容見第三章)
# HPKBUILD、hnp.json、README.OpenSource、HPKCHECK
# 可以使用 cat heredoc 方式創(chuàng)建(避免 CRLF 問題)

# 3. 進入 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 框架會依次執(zhí)行 prepare()、build()、package()、archive() 四個階段:

關(guān)鍵步驟說明

  1. prepare() 階段
    1. 復制源碼到 jna-5.14.0/ 目錄
    2. 使用 sed 修改 dispatch.c 禁用 JAWT
    3. 運行 ant javah 生成 6 個 JNI 頭文件
    4. 復制頭文件到 build/native/ 目錄
  2. build() 階段
    1. 配置并交叉編譯 libffi 靜態(tài)庫(使用 aarch64-linux-android)
    2. 編譯 dispatch.c 和 closures.c
    3. 鏈接生成 libjnidispatch.so
  3. package() 階段
    1. 將 libjnidispatch.so 復制到 usr/lib/ 目錄
  4. archive() 階段
    1. 生成 jna_5.14.0.tar.gz 標準壓縮包
    2. 生成 jna-native.hnp 鴻蒙安裝包

4.4 驗證產(chǎn)物

構(gòu)建成功后,編譯產(chǎn)物會統(tǒng)一輸出至 output 目錄,包含標準 tar 壓縮包 與鴻蒙專用 hnp 格式包

查看產(chǎn)物文件

# 1. 查看 output 目錄
cd /home/weishuo/lycium_plusplus/lycium/output/arm64-v8a
ls -lh

驗證 tar.gz 內(nèi)容

# 查看壓縮包內(nèi)容
tar tzf jna_5.14.0.tar.gz

五、鴻蒙 PC 真機驗證:JNA

鴻蒙PC 環(huán)境下完成 JNA 動態(tài)庫編譯后,需要將 libjnidispatch.so 部署到設(shè)備并做全面校驗,確保其格式、架構(gòu)、依賴和導出符號均滿足運行要求。下面分步介紹驗證流程及所用命令,并提供一個自動化驗證腳本

5.1 部署庫文件:解壓與自簽名

首先將編譯產(chǎn)物放入設(shè)備,解壓歸檔文件,為 .so 文件添加鴻蒙系統(tǒng)所需的簽名和執(zhí)行權(quán)限。

# 查看當前目錄文件(確認壓縮包存在)
ls

# 靜默解壓 jna 壓縮包(無警告輸出)
tar -zxf jna_5.14.0.tar.gz 2>/dev/null

# 進入庫文件目錄
cd usr/lib

# 鴻蒙系統(tǒng)二進制文件自簽名
binary-sign-tool sign -inFile libjnidispatch.so -outFile libjnidispatch.so -selfSign "1"

# 添加可執(zhí)行權(quán)限(必須,否則系統(tǒng)無法加載)
chmod +x libjnidispatch.so

# 驗證最終文件狀態(tài)
ls -l

說明

  • tar -zxf … 2>/dev/null:靜默解壓,避免輸出干擾
  • binary-sign-tool sign -selfSign “1”:為動態(tài)庫添加 鴻蒙PC 自簽名,系統(tǒng)在加載時會校驗簽名,未簽名文件會被拒絕
  • chmod +x:確保運行時加載器能夠映射并執(zhí)行該文件

5.2 基礎(chǔ)信息校驗:文件類型與動態(tài)依賴

確認庫文件是合法的 ELF 動態(tài)庫,并檢查其依賴關(guān)系,排除對圖形庫(如 X11)的非預期依賴。

# 查看 libjnidispatch.so 的文件類型
# 輸出顯示它是 aarch64 架構(gòu)的 ELF 動態(tài)庫,格式本身沒問題
file libjnidispatch.so

# 讀取 ELF 文件的動態(tài)節(jié)信息
# 這里可以看到它只依賴了 libc.so,SONAME 是 ../build/native/libjnidispatch.so
readelf -d libjnidispatch.so

分析

  • file 輸出確認該文件為 64?bit aarch64 ELF 共享對象,格式正確,無損壞。
  • readelf -d 顯示的 NEEDED 列表僅包含 libc.so,說明該庫除了標準 C 庫外沒有其他運行時依賴(如 libm、libdl 等),在 鴻蒙PC 環(huán)境下加載風險極低。
  • SONAME 字段為 …/build/native/libjnidispatch.so,雖帶相對路徑,但不影響 Native.loadLibrary() 找到同名文件,屬于正常。

5.3 導出符號檢查:確認 JNI 接口完整

JNA 通過 JNI 調(diào)用本地方法,因此必須確保 libjnidispatch.so 正確導出了所有 Java_ 開頭的橋接函數(shù)。

# nm 命令:列出目標文件/庫的符號表
# -D 參數(shù):只列出動態(tài)符號(即對外導出、運行時可見的符號)
nm -D libjnidispatch.so | grep "T Java_"
#  | grep "T Java_":過濾出類型為 T(text,即代碼段)且以 Java_ 開頭的符號
# 這些符號是 JNA 橋接層的核心本地方法,Java 側(cè)要調(diào)用它們,必須在 .so 里存在且導出

說明

  • nm -D 僅顯示動態(tài)符號表,T 表示全局代碼符號
  • 過濾出的 Java_com_sun_jna_* 均為 JNA 的核心本地接口,全部以 T 形式存在,說明導出表完整,Java 層調(diào)用不會因符號丟失而失敗。

5.4 架構(gòu)匹配確認

通過 ELF 文件頭再次驗證目標架構(gòu),確保庫與設(shè)備 CPU 完全匹配。

# 讀取 ELF 文件頭信息,過濾出 Class 和 Machine 字段
# -h 表示讀取文件頭(Header),grep 用來篩選關(guān)鍵信息
readelf -h libjnidispatch.so | grep "Class\|Machine"

輸出解釋

  • Class: ELF64:說明該庫為 64 位格式,只能運行在 64 位系統(tǒng)上,無法兼容 32 位環(huán)境。
  • Machine: AArch64:表示目標指令集是 ARM64,與鴻蒙 PC 設(shè)備的 aarch64 架構(gòu)一致,不存在交叉編譯錯誤。

5.5 自動化驗證腳本

為了快速完成上述檢查并生成測試代碼,可以編寫一個驗證腳本 verify_jna.sh,它會依次檢驗環(huán)境、格式、依賴、導出符號和 Java 運行時,并在條件滿足時幫助編譯、運行一個簡單的 JNA 測試程序。

腳本內(nèi)容

#!/bin/sh
# JNA OpenHarmony 真機驗證腳本
# 使用方法:./verify_jna.sh
# 注意:使用 /bin/sh 確保鴻蒙 PC 兼容性

echo "============================================================"
echo "JNA OpenHarmony 真機驗證"
echo "============================================================"

# 1. 檢查當前目錄
echo ""
echo "[1/6] 檢查當前環(huán)境..."
CURRENT_DIR=$(pwd)
echo "當前目錄: $CURRENT_DIR"

# 檢查 libjnidispatch.so 是否存在
if [ -f "libjnidispatch.so" ]; then
    echo "? 找到 libjnidispatch.so"
else
    echo "? 未找到 libjnidispatch.so"
    echo "   請在 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. 檢查動態(tài)依賴
echo ""
echo "[3/6] 檢查動態(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 依賴(不應該有)
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 導出符號
echo ""
echo "[4/6] 檢查 JNI 導出符號..."
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 個 JNI 導出符號"
    echo ""
    echo "前 10 個 JNI 函數(shù):"
    nm -D libjnidispatch.so | grep "T Java_" | head -10
else
    echo "??  未發(fā)現(xiàn) JNI 導出符號(可能 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)建測試程序
        echo ""
        echo "創(chuàng)建測試程序..."
        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 驗證測試");
        System.out.println("========================================");
        
        try {
            System.out.println("\n[測試] 調(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("請設(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? 測試失敗: " + e.getMessage());
            e.printStackTrace();
            System.exit(1);
        }
    }
}
JAVA_EOF
        
        echo "? 測試程序創(chuàng)建成功"
        echo ""
        echo "============================================================"
        echo "? 所有檢查通過!運行測試:"
        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 "   請下載: https://repo1.maven.org/maven2/net/java/dev/jna/jna/5.14.0/jna-5.14.0.jar"
        echo ""
        echo "  下載后運行:"
        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 運行時才能測試 JNA"
fi

# 最終總結(jié)
echo ""
echo "============================================================"
echo "驗證總結(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 導出符號正常($JNI_COUNT 個)"
fi
echo ""
echo "結(jié)論:JNA 本地庫已成功適配到 OpenHarmony!"
echo "============================================================"

腳本分析

該腳本分為六個檢查步驟:

  1. 環(huán)境檢查 —— 確認當前目錄包含 libjnidispatch.so
  2. ELF** 格式檢驗** —— 利用 file 命令驗證格式是否為 64 位 ARM64 ELF,防止文件損壞或架構(gòu)錯誤
  3. 動態(tài)依賴掃描 —— 通過 readelf -d 列出 NEEDED 項,確保沒有 X11 等不該出現(xiàn)的依賴,僅依賴 libc.so。
  4. JNI 符號導出驗證 —— 統(tǒng)計并展示 Java_ 開頭的全局符號,確認橋接層接口完整
  5. 架構(gòu)詳情確認 —— 再次用 readelf -h 展示 ELF 頭中的 Class 和 Machine 字段,增強可讀性
  6. Java 環(huán)境與測試 —— 檢測 Java 是否可用,查找 JNA JAR 包,生成并編譯 JNATest.java,給出運行指令。若庫文件和 Jar 包就緒,可直接編譯運行一個調(diào)用 libc.printf 的簡單示例來最終驗證 JNA 是否能正常加載并工作

通過這套自動化和人工結(jié)合的校驗流程,可以確定 libjnidispatch.so 已成功適配至 鴻蒙PC 平臺,格式完整、依賴純凈、接口齊全,能夠為上層 Java 應用提供可靠的 JNA 本地調(diào)用支持

六、常見問題與解決方案(FAQ)

6.1 編譯錯誤類

Q1:構(gòu)建時報錯 Hunk #1 FAILED at 113 或 malformed patch at line 20?

A:這是 patch 文件上下文行號不匹配導致的。原因可能是:

  1. JNA 版本不同,dispatch.c 的實際行號有差異
  2. 文件有 CRLF 換行符,導致 patch 解析失敗
  3. 之前的修改已經(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:編譯時報錯 fatal error: ‘X11/Xlib.h’ file not found?

A:原因是 JDK 的 jawt_md.h 硬編碼需要 X11 頭文件,但 OpenHarmony 是無頭系統(tǒng)(headless),不需要圖形界面。HPKBUILD 中已包含自動修復邏輯,通過 sed 修改 dispatch.c 禁用 JAWT。

# HPKBUILD 的 prepare() 函數(shù)中已包含自動修復代碼
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 會包含 jawt_md.h
  • jawt_md.h 第 29 行有 #include <X11/Xlib.h>
  • 通過定義 NO_JAWT 宏并修改條件編譯,跳過 JAWT 相關(guān)代碼

Q3:編譯時報錯 configure: error: cannot run C compiled programs. If you meant to cross compile, use ‘–host’?

A:原因是 libffi 的 configure 腳本未識別交叉編譯環(huán)境。需要在 make 命令中添加 HOST_CONFIG 和 FFI_CONFIG 參數(shù),使用 aarch64-linux-android 而不是 aarch64-linux-ohos(老版本 libffi 不認識 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 不認識 ohos 目標
  • aarch64-linux-android 與 鴻蒙PC 兼容(都是 musl libc)
  • 這是經(jīng)過驗證的可行方案

Q4:編譯時報錯 libtool: error: cannot build a shared library?

A:原因是 LDFLAGS 環(huán)境變量中包含了 -shared 標志,傳遞給了 libffi 的構(gòu)建,但 libffi 配置為靜態(tài)庫(–disable-shared),產(chǎn)生沖突。

解決方案:不設(shè)置全局 LDFLAGS 環(huán)境變量,讓 Makefile 使用自己的默認值。

# ? 錯誤做法:設(shè)置 LDFLAGS 會影響 libffi
export LDFLAGS="--target=aarch64-linux-ohos --sysroot=${OHOS_SDK}/native/sysroot -shared"

# ? 正確做法:不設(shè)置 LDFLAGS,讓 Makefile 處理
# Makefile 中已有:LDFLAGS=-o $@ -shared (用于鏈接 libjnidispatch.so)

Q5:編譯時報錯 fatal error: ‘com_sun_jna_Function.h’ file not found?

A:原因是 JNI 頭文件未生成。需要安裝 JDK 11 和 Ant,并在 prepare() 階段運行 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. 復制到頭文件目錄(Makefile 期望的位置)
mkdir -p build/native
cp -f build/headers/*.h build/native/

注意:make clean 會刪除 build/native 目錄,需要在 make clean 后重新復制頭文件。

Q6:編譯時報錯 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:編譯時報錯 ld.lld: error: undefined symbol: main

A:原因是鏈接共享庫時缺少 -shared 標志,鏈接器誤以為在構(gòu)建可執(zhí)行文件。這是因為 LDFLAGS 環(huán)境變量覆蓋了 Makefile 的默認值。

解決方案:刪除 LDFLAGS 環(huán)境變量,讓 Makefile 使用默認配置。

Q8:構(gòu)建時報錯 $‘\r’: command not found?**

A:這是 Windows 換行符(CRLF)導致的問題。在 WSL + Windows 共享目錄環(huán)境下,使用 Windows 編輯器創(chuàng)建的文件會自動帶有 CRLF 換行符。解決方案是使用 Python 腳本修復。

# 創(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:這通常是因為 hpk_build.csv 中已有該包的構(gòu)建記錄,導致構(gòu)建被跳過。需要手動清理構(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:編譯時報錯 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:如何驗證生成的庫文件是否正確?

A:使用 file 和 nm 命令檢查 ELF 文件格式和導出符號。

# 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 導出符號
nm -D ~/lycium_plusplus/lycium/usr/jna/arm64-v8a/usr/lib/libjnidispatch.so | grep "T Java_"

# 應該看到類似輸出:
# 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 使用與驗證類

Q13:如何在鴻蒙 PC 上使用 JNA?

A:JNA 是純運行時 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");
    }
}
# 運行 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 是純運行時 Java 庫,其 Java 端調(diào)用完全不需要 C 頭文件(如 dispatch.h、protect.h、ffi.h)。libjnidispatch.so 已靜態(tài)鏈接 libffi,所有底層調(diào)度由 JNA jar 包自動完成。用戶只需提供 .so 文件,無需打包或安裝任何 .h 頭文件。

Q15:如何系統(tǒng)排查構(gòu)建錯誤?

A:按以下步驟排查:

# 1. 查看完整日志
./build.sh jna 2>&1 | tee build.log

# 2. 確認失敗階段: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() - 確認 destdir 路徑和庫文件是否存在

# 6. 檢查 archive() - 確認打包路徑是否正確

Q16:為什么選擇本地源碼而非 Git 下載?

A:本地源碼模式更適合快速迭代開發(fā),避免每次構(gòu)建都重新下載。JNA 包含 native 目錄和 Java 代碼,本地源碼可以確保版本一致性。對于已經(jīng)驗證過的穩(wěn)定版本,可以改為從 GitHub 下載。

# 改為從 GitHub 下載(修改 HPKBUILD)
source="https://github.com/java-native-access/jna/archive/refs/tags/${pkgver}.tar.gz"
autounpack=true
downloadpackage=true

Q17:測試時遇到 UnsatisfiedLinkError 怎么辦?

A:這通常是庫路徑設(shè)置問題。按以下步驟排查:

# 1. 檢查 LD_LIBRARY_PATH
echo $LD_LIBRARY_PATH
# 應該包含 ./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 平臺,完整驗證了 Java JNI 本地庫在鴻蒙環(huán)境下的交叉編譯、構(gòu)建打包與依賴處理流程,形成了可復用的適配范式。通過規(guī)范 HPKBUILD 配置、禁用 JAWT 避免 X11 依賴、使用 android 兼容方案交叉編譯 libffi、自動生成 JNI 頭文件等關(guān)鍵處理,實現(xiàn)了庫正常編譯運行與輕量化部署,相關(guān)思路可廣泛遷移至各類 Java JNI 本地庫的鴻蒙移植工作。

  • 建立了 Makefile 類項目標準化的 HPKBUILD 適配模板,明確交叉編譯與 JNI 頭文件生成要點
  • 解決 X11 圖形依賴、libffi 交叉編譯、JNI 頭文件自動生成、共享庫鏈接配置等常見適配問題
  • 通過純運行時庫設(shè)計實現(xiàn)零頭文件交付,提升在鴻蒙設(shè)備上的部署效率
  • 適配方案具備通用性,可直接用于其他 Java JNI 本地庫(如 JNR、JNIWrapper)移植
  • 為后續(xù)多架構(gòu)擴展、Java 應用集成、自動化適配工具開發(fā)奠定基礎(chǔ)

八、結(jié)語

本次實踐成功將 JNA 本地庫移植至 鴻蒙PC 平臺,充分體現(xiàn)了 lycium_plusplus 框架對 Java JNI 項目交叉編譯的支撐能力。通過合理運用 HPKBUILD 構(gòu)建流程、禁用 JAWT 避免 X11 依賴、使用 android 兼容方案、規(guī)范 Make 編譯配置、自動生成 JNI 頭文件等關(guān)鍵措施,有效解決了適配中的兼容性與構(gòu)建問題,為同類開源 Java JNI 本地庫遷移至鴻蒙生態(tài)提供了可復用的思路與實踐參考,也助力開源鴻蒙原生工具生態(tài)的完善與發(fā)展。

提示:本文基于 JNA 5.14.0 版本進行適配。不同版本的依賴和構(gòu)建腳本可能有所差異,建議在適配前先熟悉目標項目的 Makefile 和構(gòu)建配置文件。JNA 是純運行時庫,Java 端使用不需要任何 C 頭文件,只需提供 libjnidispatch.so 配合 jna.jar 即可。

到此這篇關(guān)于[鴻蒙PC三方庫適配]Java本地訪問庫JNA適配到鴻蒙PC平臺的文章就介紹到這了,更多相關(guān)JNA本地庫適配到鴻蒙PC平臺內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 基于Failed?to?load?ApplicationContext異常的解決思路

    基于Failed?to?load?ApplicationContext異常的解決思路

    這篇文章主要介紹了基于Failed?to?load?ApplicationContext異常的解決思路,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • SpringBoot詳解如何實現(xiàn)讀寫分離

    SpringBoot詳解如何實現(xiàn)讀寫分離

    當響應的瓶頸在數(shù)據(jù)庫的時候,就要考慮數(shù)據(jù)庫的讀寫分離,當然還可以分庫分表,那是單表數(shù)據(jù)量特別大,當單表數(shù)據(jù)量不是特別大,但是請求量比較大的時候,就要考慮讀寫分離了.具體的話,還是要看自己的業(yè)務...如果還是很慢,那就要分庫分表了...我們這篇就簡單講一下讀寫分離
    2022-05-05
  • 淺談Java枚舉的作用與好處

    淺談Java枚舉的作用與好處

    下面小編就為大家?guī)硪黄獪\談Java枚舉的作用與好處。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2016-07-07
  • Java程序開發(fā)環(huán)境配置圖文教程

    Java程序開發(fā)環(huán)境配置圖文教程

    這篇文章主要為大家詳細介紹了Java程序開發(fā)環(huán)境配置圖文教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2019-07-07
  • Java中的參數(shù)傳遞詳細介紹

    Java中的參數(shù)傳遞詳細介紹

    大家好,本篇文章主要講的是Java中的參數(shù)傳遞詳解,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽
    2022-01-01
  • Spring Security基于json登錄實現(xiàn)過程詳解

    Spring Security基于json登錄實現(xiàn)過程詳解

    這篇文章主要介紹了Spring Security基于json登錄實現(xiàn)過程詳解,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-08-08
  • springboot配置多數(shù)據(jù)源(靜態(tài)和動態(tài)數(shù)據(jù)源)

    springboot配置多數(shù)據(jù)源(靜態(tài)和動態(tài)數(shù)據(jù)源)

    在開發(fā)過程中,很多時候都會有垮數(shù)據(jù)庫操作數(shù)據(jù)的情況,需要同時配置多套數(shù)據(jù)源,本文主要介紹了springboot配置多數(shù)據(jù)源(靜態(tài)和動態(tài)數(shù)據(jù)源),感興趣的可以了解一下
    2023-09-09
  • JAVA實現(xiàn)經(jīng)典游戲坦克大戰(zhàn)的示例代碼

    JAVA實現(xiàn)經(jīng)典游戲坦克大戰(zhàn)的示例代碼

    小時候大家都玩過坦克大戰(zhàn)吧,熟悉的旋律和豐富的關(guān)卡陪伴了我們一整個寒暑假。本文將通過Java+Swing實現(xiàn)這一經(jīng)典游戲,感興趣的可以學習一下
    2022-01-01
  • springboot security之前后端分離配置方式

    springboot security之前后端分離配置方式

    這篇文章主要介紹了springboot security之前后端分離配置方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2025-03-03
  • Java中冒泡排序的原生實現(xiàn)方法(正序與逆序)

    Java中冒泡排序的原生實現(xiàn)方法(正序與逆序)

    這篇文章主要給大家介紹了關(guān)于Java中冒泡排序的原生實現(xiàn)方法(正序與逆序)的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2020-11-11

最新評論

绥德县| 屏东县| 顺平县| 禹州市| 南开区| 资中县| 临武县| 台南县| 海兴县| 涟源市| 堆龙德庆县| 长白| 香港 | 新巴尔虎右旗| 隆安县| 昭苏县| 沂源县| 无极县| 黑山县| 禄丰县| 长丰县| 安远县| 民县| 简阳市| 子洲县| 沾益县| 泾阳县| 长子县| 温宿县| 大姚县| 城口县| 衡山县| 白山市| 山西省| 新郑市| 玉龙| 年辖:市辖区| 年辖:市辖区| 马公市| 府谷县| 军事|