在Java中使用panama FFI呼叫Rust庫

banq發表於2021-09-15

如何在Java中呼叫Rust編寫的庫包?在這個例子中,我們將看到如何:
  • 構建一個簡單的 Rust 庫,它公開一個 C API(巴拿馬 FFI 可以連結到它)。
  • 使用cbindgen來生成這個庫中的C標頭檔案。
  • 用於jextract從標頭檔案生成 java 繫結。
  • 建立一個簡單的 java 程式,透過繫結呼叫 rust 庫。

 
步驟 1. 設定專案

$ mkdir rust-panama-helloworld
$ cd rust-panama-helloworld
$ cargo init --lib

 
步驟 2. 編寫一個簡單的 Rust 庫
編輯src/lib.rs並將內容更改為:

#[no_mangle]
pub extern "C" fn hello_world() {
    println!("Hello, world!");
}

#[no_mangle]需要該屬性以確保函式在庫中可見,並extern "C"用於確保函式具有正確的 ABI(特定平臺的 C ABI)。
 

步驟 3. 新增所需的專案配置
進入Cargo.toml並新增以下內容:

[build-dependencies]
cbindgen = "0.20.0"

<p class="indent">[lib]
crate_type = ["cdylib"]

cbindgen用於從 rust 源生成 C 標頭檔案,該檔案將被輸入到巴拿馬的jextract工具中以生成 java 繫結。
 

步驟 4. 建立一個呼叫 cbindgen 的構建指令碼
build.rs在頂級目錄中建立一個檔案並新增以下內容:

extern crate cbindgen;

use std::env;

fn main() {
    let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap();

    cbindgen::Builder::new()
      .with_crate(crate_dir)
      .with_language(cbindgen::Language::C)
      .generate()
      .expect("Unable to generate bindings")
      .write_to_file("lib.h");
}

這是一個簡單的構建指令碼,它呼叫 cbindgen 並將輸出寫入lib.h頂級目錄中的檔案。
 

步驟 5. 構建 Rust 庫並生成 C 標頭檔案
$ cargo build
這應該在target/debug的資料夾中建立該檔案(lib)rust_panama_helloworld.(dll/so/dylib)。
構建還應呼叫cbindgen並生成lib.h頂級目錄中的檔案。該檔案的內容應如下所示:

include <stdarg.h>
include <stdbool.h>
include <stdint.h>
include <stdlib.h>

void hello_world(void);


 
步驟 6. 確保panama  jdk 在路徑上

$ java --version
openjdk 17-panama 2021-09-14
OpenJDK Runtime Environment (build 17-panama+3-167)
OpenJDK 64-Bit Server VM (build 17-panama+3-167, mixed mode, sharing)

在撰寫本文時,我正在使用http://jdk.java.net/panama/上提供的最新快照。
 

步驟 7. 使用 jextract 生成 java 繫結

$ jextract -d classes -t org.openjdk --include-function hello_world -l rust_panama_helloworld -- lib.h


在這個命令中:
  • -d classes 指定生成的繫結的輸出目錄。
  • -t org.openjdk 指定生成的類所在的 java 包。
  • --include-function hello_world所以我們只包含hello_world我們在 rust 中定義的函式。
  • -l rust_panama_helloworld使生成的繫結將rust_panama_helloworld自動載入庫。
  • -- 是一個分隔符,用於指示要處理的標頭檔案列表的開始。
  • 最後lib.h是在步驟 5 中生成的標頭檔案。

一個classes目錄應建立包含由jextract產生了一些檔案:

./classes
└───org
    └───openjdk
            constants$0.class
            lib_h.class
            RuntimeHelper$VarargsInvoker.class
            RuntimeHelper.class




 

步驟 8. 建立一個呼叫我們的庫的 java 程式
在頂級目錄中建立一個Main.java檔案並將以下內容放入其中:

import static org.openjdk.lib_h.*;

public class Main {
    public static void main(String[] args) {
        hello_world();
    }
}

 

步驟 9. 執行 java 程式

$ java --add-modules jdk.incubator.foreign --enable-native-access=ALL-UNNAMED -Djava.library.path=./target/debug -cp classes Main.java

在這個命令中:
  • --add-modules jdk.incubator.foreign將jdk.incubator.foreign模組新增到模組圖中(預設情況下不解析孵化器模組)。
  • --enable-native-access=ALL-UNNAMED 啟用對未命名模組的本機訪問,這是巴拿馬 API 工作所必需的。
  • -Djava.library.path=./target/debug將包含我們 rust 庫的目錄新增到庫路徑中,以便System.loadLibrary在執行時可以找到它。
  • -cp classes 使用我們生成的 java 繫結類(jextract 的輸出)指定類路徑。
  • Main.java 指定我們的主類,它將被動態編譯,然後執行。

 

步驟 10. 觀察輸出

WARNING: Using incubator modules: jdk.incubator.foreign
warning: using incubating module(s): jdk.incubator.foreign
1 warning
Hello, world!









 

相關文章