Mic*_*ael 30 java java-native-interface rust
我正在使用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;
}
Run Code Online (Sandbox Code Playgroud)
然后我可以从Java类test.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));
}
}
Run Code Online (Sandbox Code Playgroud)
运行Java main会打印出预期的结果:
hello from rust
42
Run Code Online (Sandbox Code Playgroud)
在Rust方法中,我声明env为指向该Any类型的指针,但实际上它是一个带有函数指针的C结构,如文档(代码示例4-1)中所述,它们是与Java运行时交换数据所必需的.
从这个答案我明白,这些带有函数指针的结构应该在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,
…
Run Code Online (Sandbox Code Playgroud)
当我从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);
}
}
Run Code Online (Sandbox Code Playgroud)
我该如何正确调用GetVersion函数?请注意,我对这类内容很新,所以如果需要,请随时编辑此问题.
Vla*_*eev 17
除了这个问题*mut Any/ *const Any胖三分球,还有一个事实,即原生JNI函数使用双间接访问时JNINativeInterface结构:
struct JNINativeInterface_;
typedef const struct JNINativeInterface_ *JNIEnv;
jint (JNICALL *GetVersion)(JNIEnv *env);
Run Code Online (Sandbox Code Playgroud)
在这里,您可以看到这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);
}
}
Run Code Online (Sandbox Code Playgroud)
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();
}
}
Run Code Online (Sandbox Code Playgroud)
调用:
% javac tests/Test.java
% java tests.Test
Invoked native method, jre: 0x7f81240011e0, class: 0x10d9808d8
version: 65544
Run Code Online (Sandbox Code Playgroud)
65544是0x10008,实际上,我在Oracle JVM 1.8下运行它.
我猜你可以省略_opaque_data字段,因为JNINativeInterface结构总是通过指针传递,所以如果你只需要结构中的几个第一个字段,你可以只声明它们而忽略其余的字段.
Sur*_*esh 11
一种更简单的方法是使用JnrFFI.JRuby项目大量使用JnrFFI,它很可能构成新的Java FFI JEP的基础.这基本上消除了编写所有JNI废话.以下是使用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);
}
Run Code Online (Sandbox Code Playgroud)
锈编码
#[no_mangle]
pub extern fn double_input(input: i32) -> i32 {
input * 2
}
Run Code Online (Sandbox Code Playgroud)
这是完整的代码
从 java 调用 Rust 代码的另一种方法也是最佳方法是使用 GraalVM。在我看来,如果性能是您的情况的主要关键因素,那么这是最好的选择。
\nGraalVM 是一种高性能运行时,可显着提高应用程序性能和效率,非常适合微服务。它适用于使用动态语言 java 和 Javascript 以及基于 LLVM 的语言(如 C C++ 和 Rust)创建的应用程序。它打破了编程语言之间的界限,使共享运行时互操作性成为可能。它可以独立运行,也可以与 OpenJDK、Node.js 或 Oracle 数据库结合运行。
\nGraalVM 可以与 OpenJDK 结合使用,利用新的即时编译技术来加速 Java 程序的性能。Java 的字节码由 GraalVM 转换为机器码。这种安排可能是有利的,特别是对于 Scala 等其他基于 JVM 的语言,正如 Twitter 在生产中运行 GraalVM 所证明的那样。
\nGraalVM 编译器为高度抽象的应用程序提供了性能优势,因为它经常可以消除昂贵的对象分配。详细信息可以在这篇研究文章中找到。
\n让我们编码: \n假设我们有一个示例,我们想要从 java 调用一个函数,使用一个/两个参数来 rust 并将结果返回到 java,代码将如下所示。
\n假设您的计算机中的所有设置均已正确,我们可以继续进行 llvm 安装(您可以在此处遵循本指南)。
\n可以使用 GraalVM Updater 工具按需将 LLVM 工具链添加到 GraalVM
\n$GRAALVM_HOME/bin/gu install llvm-toolchain\nRun Code Online (Sandbox Code Playgroud)\n上述命令将为 GraalVM 社区用户从 GitHub 目录安装 LLVM 工具链。
\nexport LLVM_TOOLCHAIN=$($JAVA_HOME/bin/lli --print-toolchain-path)\nRun Code Online (Sandbox Code Playgroud)\n锈
\n让\xe2\x80\x99s 看看rustpart.rs,它是一个标准的 Rust 函数,它接受一个数字来找到它的立方根并返回它。但我们确实必须指定 #[no_mangle] 注释,并且显然我们也不能使用任何板条箱。具有原始参数/输出的简单函数似乎可以工作,但更复杂的函数在嵌入时不起作用:
$GRAALVM_HOME/bin/gu install llvm-toolchain\nRun Code Online (Sandbox Code Playgroud)\n我们使用带有以下标志的 rustc 编译器将 Rust 源代码编译为二进制代码--emit=llvm-bc:
export LLVM_TOOLCHAIN=$($JAVA_HOME/bin/lli --print-toolchain-path)\nRun Code Online (Sandbox Code Playgroud)\n需要注意的是,我们将使用 java 生成的 .bc 文件,而不是 .rs 文件
\n爪哇
\n现在让我们转向 Java 代码,只需在 pom.xml 中添加此依赖项
\n <dependency>\n <groupId>org.graalvm.sdk</groupId>\n <artifactId>graal-sdk</artifactId>\n <version>22.2.0</version>\n </dependency>\nRun Code Online (Sandbox Code Playgroud)\njava 代码将如下所示:
\n #[no_mangle]\n fn cube_root(arg: f64) -> f64 {\n arg.cbrt()\n }\n \n fn main(){}\nRun Code Online (Sandbox Code Playgroud)\n瞧瞧!!!!
\n锈
\nRust lib.rs:
\n rustc --emit=llvm-bc rustpart.rs\nRun Code Online (Sandbox Code Playgroud)\n货物文件:
\n[package]\nname = "rustlib"\nversion = "0.1.0"\nedition = "2018"\n\n\n[[bin]]\nname = "lib"\npath = "src/main.rs"\n\n[build]\nrustflags = ["-C", "target-cpu=native"]\n\n[lib]\nname = "rustlib"\npath = "src/lib.rs"\ncrate-type = ["cdylib"]\n\n[dependencies]\nj4rs = "0.12"\nj4rs_derive = "0.1"\nserde = { version = "1.0", features = ["derive"] }\nRun Code Online (Sandbox Code Playgroud)\n如果我们在 Windows 系统上,我们执行以下命令来生成 .dll 库:
\ncargo build --lib\nRun Code Online (Sandbox Code Playgroud)\n之后我们可以观察target/debugpath下创建的.dll文件
爪哇
\n我们在 pom.xml 中包含以下依赖项
\n<dependency>\n <groupId>io.github.astonbitecode</groupId>\n <artifactId>j4rs</artifactId>\n <version>0.12.0</version>\n</dependency>\nRun Code Online (Sandbox Code Playgroud)\njava 代码看起来像这样,这里是 RustFunctionCalls
\n <dependency>\n <groupId>org.graalvm.sdk</groupId>\n <artifactId>graal-sdk</artifactId>\n <version>22.2.0</version>\n </dependency>\nRun Code Online (Sandbox Code Playgroud)\n这是我们所说的主要内容:
\npackage io.example;\n\nimport org.graalvm.polyglot.Context;\nimport org.graalvm.polyglot.Source;\nimport org.graalvm.polyglot.Value;\n\nimport java.io.File;\nimport java.io.IOException;\n\npublic class App3 {\n public static void main(String[] args) throws IOException {\n File file=new File("generated.bc");\n Context context = Context.newBuilder().allowAllAccess(true).build();\n Source source = Source.newBuilder("llvm", file).build();\n context.eval(source);\n Value ruspart= context.getBindings("llvm").getMember("cube_root");\n Double cubeRoot = ruspart.execute(10).asDouble();\n System.out.println(cubeRoot);\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n通过这个简单的示例,您可以从 java 调用 rust
\n