【问题标题】:Calling Rust from Java从 Java 调用 Rust
【发布时间】:2015-05-15 11:31:07
【问题描述】:

我正在使用 Rust 1.0 beta,并且能够创建一个小示例,用于从 Java 调用用 Rust 编写的函数。我只是使用 rustc 在 mylib.rs 中编译了以下 Rust 代码,它在 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 类 tests.Test 中调用这些函数:

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 方法中,我将 env 声明为指向 Any 类型的指针,但实际上它是一个 C 结构,带有指向 documentation (Code example 4-1) 中描述的函数的指针,这些函数是与 Java 运行时交换数据所必需的.

从answer 我了解到,这种带有函数指针的结构在 Rust 代码中应该有一个对应物来调用这些函数。所以我尝试实现这样一个结构,将所有字段值设置为*mut Any,GetVersion 字段除外:

#[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 函数?请注意,我对这类东西真的很陌生,所以如果需要,请随时编辑这个问题。

【问题讨论】:

  • 我无法给出完整的答案,但您似乎假设 *const Any 是一个常规指针:它不是。它是一个指向 trait 对象的指针,这意味着它是常规指针大小的两倍。您不应该使用*const Any 来表示“任意指针”;为此,您应该使用*const () 或*const c_void(来自libc 包)。顺便说一句:您可以使用::std::mem::size_of<T>() 来获取给定类型的大小。
  • 请注意Any是一个特征,任何指向一个裸特征的指针都是一个特征对象,也就是说,它实际上是一个“胖”指针。例如,this 程序打印 16 而不是 8。因此它不适合用作类似void* 的指针。你需要例如*mut () 为它。

标签: java java-native-interface rust


【解决方案1】:

除了*mut Any/*const Any是胖指针的问题外,还有一个事实是原生JNI函数在访问JNINativeInterface结构时使用了双重间接:

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

在这里,您可以看到JNIEnv 是指向JNINativeInterface_ 结构的指针,该结构实际​​上包含您提供的字段,而GetVersion 接受指向JNIEnv 的指针——也就是说,它需要一个指向指针的指针到JNINativeInterface_。这个 Rust 程序可以在我的机器上运行(Rust nightly 被使用,但相同的代码可以在 beta 版中使用外部 libc crate):

#![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_data 字段,因为 JNINativeInterface 结构总是通过指针传递,所以如果你只需要结构中的几个 first 字段,你可以只声明它们并忽略休息。

【讨论】:

    【解决方案2】:

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

    Java 代码

      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 代码

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

    这里是full code

    【讨论】:

    • 感谢您的示例,Rust 业务代码不应该适应使用它的应用程序。如果您有一些遵循这种想法的资源,那将非常有用!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-09-19
    • 2018-02-18
    • 2019-05-19
    • 2014-01-24
    • 1970-01-01
    • 2015-11-05
    • 1970-01-01
    相关资源
    最近更新 更多