从 Java 调用 Rust

2022-09-01 01:08:15

我正在使用 Rust 1.0 beta,并且能够创建一个小示例来调用从 Java 用 Rust 编写的函数。我只是在 mylib.rs 中使用rustc编译了以下Rust代码,rustc在Windows上生成了mylib.dll:

#![crate_type = "dylib"]
use std::any::Any;

#[no_mangle]
pub extern fn Java_tests_Test_hello(env: *const Any, jclass: *const Any) {
    println!("hello from rust");
}

#[no_mangle]
pub extern fn Java_tests_Test_sum(env: *const Any, jclass: *const Any, a: i32, b: i32) -> i32 {
    return a + b;
}

然后,我可以从 Java 类测试中调用这些函数。测试:

package tests;

import java.io.File;

public class Test {

    public static native void hello();

    public static native int sum(int a, int b);

    public static void main(String[] args) {
        File f = new File("mylib.dll");
        System.load(f.getAbsolutePath());
        Test.hello();
        System.out.println(Test.sum(20, 22));
    }
}

运行 Java 主将打印预期的结果:

hello from rust
42

在 Rust 方法中,我声明为指向该类型的指针,但实际上它是一个 C 结构,其中包含指向文档(代码示例 4-1)中描述的函数的指针,这些函数是与 Java 运行时交换数据所必需的。envAny

从这个答案中,我明白了这种带有函数指针的结构在 Rust 代码中应该有一个对应项来调用这些函数。因此,我试图实现这样的结构,将所有字段值设置为除字段之外:*mut AnyGetVersion

#[repr(C)]
pub struct JavaEnv {

    reserved0: *mut Any,
    reserved1: *mut Any,
    reserved2: *mut Any,
    reserved3: *mut Any,
    GetVersion: extern "C" fn(env: *mut JavaEnv) -> i32,

    DefineClass: *mut Any,
    FindClass: *mut Any,  
    …

当我从Java调用以下函数时,它应该调用GetVersion函数,JVM崩溃了:

#[no_mangle]
pub extern fn Java_tests_Test_helloJre(jre: *mut JavaEnv, class: *const Any) {
    unsafe {
        let v = ((*jre).GetVersion)(jre);
        println!("version: {:?}", v);
    }
}

我应该如何正确调用 GetVersion 函数?请注意,我对这种东西真的很陌生,所以如果需要,请随时编辑这个问题。


答案 1

除了 / 是胖指针的问题之外,还有一个事实是本机 JNI 函数在访问结构时使用双间接寻址:*mut Any*const AnyJNINativeInterface

struct JNINativeInterface_;
typedef const struct JNINativeInterface_ *JNIEnv;
jint (JNICALL *GetVersion)(JNIEnv *env);

在这里,您可以看到这是一个指向结构的指针,它实际上包含您提供的字段,并接受指向的指针 - 也就是说,它需要指向 的指针。这个 Rust 程序在我的机器上运行(使用 Rust nightly,但相同的代码可以在 beta 中使用外部 libc 板条箱):JNIEnvJNINativeInterface_GetVersionJNIEnvJNINativeInterface_

#![crate_type="dylib"]
#![feature(libc)]
extern crate libc;

use libc::c_void;

#[repr(C)]
pub struct JNINativeInterface {
    reserved0: *mut c_void,
    reserved1: *mut c_void,
    reserved2: *mut c_void,
    reserved3: *mut c_void,

    GetVersion: extern fn(env: *mut JNIEnv) -> i32,

    _opaque_data: [u8; 1824]
}

pub type JNIEnv = *const JNINativeInterface;

#[no_mangle]
pub extern fn Java_tests_Test_helloJre(jre: *mut JNIEnv, class: *const c_void) {
    println!("Invoked native method, jre: {:p}, class: {:p}", jre, class);
    unsafe {
        let v = ((**jre).GetVersion)(jre);
        println!("version: {:?}", v);
    }
}

Java 对应项:

package tests;

import java.nio.file.Path;
import java.nio.file.Paths;

public class Test {
    public static native void helloJre();

    public static void main(String[] args) {
        Path p = Paths.get("libtest.dylib");
        System.load(p.toAbsolutePath().toString());
        Test.helloJre();
    }
}

调用:

% javac tests/Test.java
% java tests.Test
Invoked native method, jre: 0x7f81240011e0, class: 0x10d9808d8
version: 65544

65544 0x10008,事实上,我在Oracle JVM 1.8下运行它。

我想你可以省略字段,因为结构总是由指针传递,所以如果你只需要结构中的几个第一个字段,你可以只声明它们,忽略其余的。_opaque_dataJNINativeInterface


答案 2

一种更简单的方法是使用JnrFFI。JRuby项目大量使用JnrFFI,它很可能构成新的Java FFI JEP的基础。这基本上消除了编写所有JNI废话。下面是使用 JnrFFI 从 Java 调用 Rust 函数的示例代码

Java Code

  public static interface RustLib {
        int double_input(int i);
    }
    public static String getLibraryPath(String dylib) {
        File f = new File(JavaRustFFI.class.getClassLoader().getResource(mapLibraryName(dylib)).getFile());
        return f.getParent();
    }
    public static void main(String[] args) {
        String dylib = "double_input";
        System.setProperty("jnr.ffi.library.path", getLibraryPath(dylib));
        RustLib rlib = LibraryLoader.create(RustLib.class).load(dylib);
        int r = rlib.double_input(20);
        System.out.println("Result from rust double_input:  " + r);
    }

Rust Code

#[no_mangle]
pub extern fn double_input(input: i32) -> i32 {
    input * 2
}

这是完整的代码


推荐