从 Java 调用 Rust
Calling Rust from Java
我正在使用 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 class 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函数?请注意,我对这类东西真的很陌生,所以如果需要,请随时编辑这个问题。
除了*mut Any
/*const Any
是胖指针的问题外,还有一个事实是本地JNI函数在访问[时使用双重间接 =16=]结构:
struct JNINativeInterface_;
typedef const struct JNINativeInterface_ *JNIEnv;
jint (JNICALL *GetVersion)(JNIEnv *env);
在这里,您可以看到 JNIEnv
是指向 JNINativeInterface_
结构的指针,它实际上包含您提供的字段,而 GetVersion
接受指向 JNIEnv
的指针 -也就是说,它需要一个指向 JNINativeInterface_
的指针。这个 Rust 程序可以在我的机器上运行(使用 Rust nightly 但相同的代码可以在测试版中使用外部 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 字段,您可以只声明它们而忽略其余部分。
一种更简单的方法是使用 JnrFFI. The JRuby project heavily uses JnrFFI and it is likely to form the basis for the new Java FFI JEP. This basically eliminates writing all the JNI nonsense. Here is 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
}
我正在使用 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 class 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函数?请注意,我对这类东西真的很陌生,所以如果需要,请随时编辑这个问题。
除了*mut Any
/*const Any
是胖指针的问题外,还有一个事实是本地JNI函数在访问[时使用双重间接 =16=]结构:
struct JNINativeInterface_;
typedef const struct JNINativeInterface_ *JNIEnv;
jint (JNICALL *GetVersion)(JNIEnv *env);
在这里,您可以看到 JNIEnv
是指向 JNINativeInterface_
结构的指针,它实际上包含您提供的字段,而 GetVersion
接受指向 JNIEnv
的指针 -也就是说,它需要一个指向 JNINativeInterface_
的指针。这个 Rust 程序可以在我的机器上运行(使用 Rust nightly 但相同的代码可以在测试版中使用外部 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 字段,您可以只声明它们而忽略其余部分。
一种更简单的方法是使用 JnrFFI. The JRuby project heavily uses JnrFFI and it is likely to form the basis for the new Java FFI JEP. This basically eliminates writing all the JNI nonsense. Here is 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
}