在Java中使用panama FFI调用Rust库


如何在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"

[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!