从Java调用Rust

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)

这是完整的代码


Pan*_*tos 5

从 java 调用 Rust 代码的另一种方法也是最佳方法是使用 GraalVM。在我看来,如果性能是您的情况的主要关键因素,那么这是最好的选择。

\n

GraalVM 是一种高性能运行时,可显着提高应用程序性能和效率,非常适合微服务。它适用于使用动态语言 java 和 Javascript 以及基于 LLVM 的语言(如 C C++ 和 Rust)创建的应用程序。它打破了编程语言之间的界限,使共享运行时互操作性成为可能。它可以独立运行,也可以与 OpenJDK、Node.js 或 Oracle 数据库结合运行。

\n

GraalVM 可以与 OpenJDK 结合使用,利用新的即时编译技术来加速 Java 程序的性能。Java 的字节码由 GraalVM 转换为机器码。这种安排可能是有利的,特别是对于 Scala 等其他基于 JVM 的语言,正如 Twitter 在生产中运行 GraalVM 所证明的那样。

\n

GraalVM 编译器为高度抽象的应用程序提供了性能优势,因为它经常可以消除昂贵的对象分配。详细信息可以在这篇研究文章中找到。

\n

让我们编码: \n假设我们有一个示例,我们想要从 java 调用一个函数,使用一个/两个参数来 rust 并将结果返回到 java,代码将如下所示。

\n

1)方法-->GraalVM

\n 首先要警告您,GraalVm 处理起来有点棘手,要熟悉它需要时间,因此如果您想要简单,请选择第二个选择。\n

假设您的计算机中的所有设置均已正确,我们可以继续进行 llvm 安装(您可以在此处遵循本指南)。

\n

可以使用 GraalVM Updater 工具按需将 LLVM 工具链添加到 GraalVM

\n
$GRAALVM_HOME/bin/gu install llvm-toolchain\n
Run Code Online (Sandbox Code Playgroud)\n

上述命令将为 GraalVM 社区用户从 GitHub 目录安装 LLVM 工具链。

\n
export LLVM_TOOLCHAIN=$($JAVA_HOME/bin/lli --print-toolchain-path)\n
Run Code Online (Sandbox Code Playgroud)\n

\n

让\xe2\x80\x99s 看看rustpart.rs,它是一个标准的 Rust 函数,它接受一个数字来找到它的立方根并返回它。但我们确实必须指定 #[no_mangle] 注释,并且显然我们也不能使用任何板条箱。具有原始参数/输出的简单函数似乎可以工作,但更复杂的函数在嵌入时不起作用:

\n
$GRAALVM_HOME/bin/gu install llvm-toolchain\n
Run Code Online (Sandbox Code Playgroud)\n

我们使用带有以下标志的 rustc 编译器将 Rust 源代码编译为二进制代码--emit=llvm-bc

\n
export LLVM_TOOLCHAIN=$($JAVA_HOME/bin/lli --print-toolchain-path)\n
Run 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>\n
Run Code Online (Sandbox Code Playgroud)\n

java 代码将如下所示:

\n
    #[no_mangle]\n    fn cube_root(arg: f64) -> f64 {\n        arg.cbrt()\n    }\n    \n    fn main(){}\n
Run Code Online (Sandbox Code Playgroud)\n

瞧瞧!!!!

\n

2)方法-->j4rs

\n从另一个角度来看,如果简单性和灵活性是主要情况,您可以使用“j4rs”作为替代。我将深入探讨细节。\n

\n

Rust lib.rs:

\n
    rustc --emit=llvm-bc rustpart.rs\n
Run 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"] }\n
Run Code Online (Sandbox Code Playgroud)\n

如果我们在 Windows 系统上,我们执行以下命令来生成 .dll 库:

\n
cargo build --lib\n
Run Code Online (Sandbox Code Playgroud)\n

之后我们可以观察target/debugpath下创建的.dll文件

\n

爪哇

\n

我们在 pom.xml 中包含以下依赖项

\n
<dependency>\n    <groupId>io.github.astonbitecode</groupId>\n    <artifactId>j4rs</artifactId>\n    <version>0.12.0</version>\n</dependency>\n
Run Code Online (Sandbox Code Playgroud)\n

java 代码看起来像这样,这里是 RustFunctionCalls

\n
 <dependency>\n            <groupId>org.graalvm.sdk</groupId>\n            <artifactId>graal-sdk</artifactId>\n            <version>22.2.0</version>\n  </dependency>\n
Run Code Online (Sandbox Code Playgroud)\n

这是我们所说的主要内容:

\n
package 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}\n
Run Code Online (Sandbox Code Playgroud)\n

通过这个简单的示例,您可以从 java 调用 rust

\n