首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

JNI 的替代方案:使用 JNR (jnr-ffi) 實現 Java 外部函數介面

     編輯:Java 相關知識

前言:為什麼我們需要 JNR?

Java Native Interface (JNI) 歷來是 Java 調用本地代碼的標準橋樑,但它的使用體驗往往令人頭疼。根據 JEP 191 的分析,JNI 的主要痛點在於:強迫開發者編寫 C/C++ 代碼、需要深入理解 JVM 內存模型、跨平台編譯部署困難、性能開銷大,且構成了一個不透明的安全邊界。為了解決這些問題,社區孕育出了 JNA (Java Native Access) 和 JNR (Java Native Runtime) 等庫。JNR 由 JRuby 團隊主導開發,提供了更豐富的抽象層次、函數與內存元數據支持,並被視為未來 JEP 191 定義的標準 Foreign Function Interface (FFI) 的有力候選基礎。掌握 JNR,是為未來 Java 原生互操作做準備。

第一步:獲取可用的 jnr-ffi.jar — 從源碼構建

JNR 專案託管於 GitHub,採用 Maven 管理。直接下載 Zip 源碼或使用 Maven 中央倉庫的舊版本往往會導致依賴缺失,構建出的 JAR 無法使用。必須從源碼構建「Fat JAR」(包含所有依賴的全量包)。

方法一:在 Eclipse 中導入 Maven 專案構建 (適合不熟悉命令行的用戶)

  1. 克隆源碼:務必使用 git clone https://github.com/jnr/jnr-ffi.git,而非網頁下載 Zip。
  2. 安裝 m2eclipse 插件:Eclipse 自帶 Maven 插件常有問題,建議從 https://download.eclipse.org/technology/m2e/releases 安裝 m2eclipse。
  3. 導入專案:選擇 Import > Existing Maven Projects,指向克隆的根目錄。
  4. 修復常見編譯錯誤:
    • maven-antrun-plugin 錯誤:在 pom.xml 的 <plugins> 外層包裹 <pluginManagement> 標籤即可解決生命周期映射缺失問題。
    • NativeClosureFactory 泛型錯誤:內部類引用外部泛型類時未保留類型參數。將 private final Native factory; 修正為 private final Native<T> factory;,構造函數同理。
  5. 打包 Fat JAR:安裝 Build Fat JAR Eclipse 插件,右鍵專案選擇 Build Fat JAR,一鍵生成包含所有依賴的可用 JAR 包。

方法二:使用 Maven 命令行構建 (推薦,更標準)

  1. 安裝 Maven 並配置環境變數。
  2. 進入專案根目錄,修改 pom.xml,在 <plugins> 區塊添加 maven-assembly-plugin 配置:
    <plugin>
      <artifactId>maven-assembly-plugin</artifactId>
      <configuration>
        <descriptorRefs>
          <descriptorRef>jar-with-dependencies</descriptorRef>
        </descriptorRefs>
      </configuration>
    </plugin>
  3. 執行 mvn assembly:assembly。
  4. 在 target/ 目錄下獲得 jnr-ffi-2.0.0-SNAPSHOT-jar-with-dependencies.jar,即為所需完整包。

提示:若構建過程遇阻,文末提供了作者預構建的 JAR 下載鏈接作為備選。

第二步:JNR 核心 API 實戰 — Hello World

將構建好的 JAR 加入專案類路徑。以下示例調用 C 標準庫 msvcrt.dll (Windows) 或 libc.so (Linux) 中的 puts() 函數。

package helloworld;

import jnr.ffi.LibraryLoader;

public class HelloWorld {
    // 1. 定義一個靜態接口,映射目標本地函數
    public static interface LibC {
        int puts(String s);
    }

    public static void main(String[] args) {
        // 2. 通過 LibraryLoader 加載庫並獲取接口實例
        LibC libc = LibraryLoader.create(LibC.class).load("msvcrt"); // Linux 上填 "c"
        // 3. 直接像調用 Java 方法一樣調用本地函數
        libc.puts("Hello, World from JNR!");
    }
}

關鍵點解析

  • 接口定義:無需繼承任何基類,純粹的 Java 接口,方法簽名需與 C 函數原型嚴格對應 (參數類型、返回值、調用約定)。
  • 庫加載:LibraryLoader.create(Interface.class).load("libName")。庫名不帶前綴(lib)和後綴(.dll/.so/.dylib),JNR 會自動按平台解析。
  • 類型映射表:JNR 提供豐富的 Java-to-Native 類型映射,核心對應關係如下:
    Java 類型Native 類型
    byteint8_t
    shortint16_t
    intint32_t
    longintptr_t (平台相關 32/64 位)
    floatfloat
    doubledouble
    Stringconst char * (UTF-8)
    Pointer / Buffervoid *

總結與展望

JNR 以純 Java 方式實現了高性能、類型安全的本地互操作,避免了 JNI 的樣板代碼與 C 編譯工具鏈依賴。隨著 Panama 專案 (JEP 191/419/422 等) 的推進,基於 JNR 架構的標準 FFI 終將內建於 JDK。提前掌握 JNR,不僅能解決當下的調用難題,更是對未來 Java 原生互操作標準的預演。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved