简体   繁体   English

从 Java 调用 Rust

[英]Calling Rust from Java

I am using Rust 1.0 beta and was able to create a small example for calling functions written in Rust from Java.我正在使用 Rust 1.0 beta 并且能够创建一个小示例,用于从 Java 调用用 Rust 编写的函数。 I simply compiled the following Rust code in mylib.rs using rustc which produces a mylib.dll on Windows:我只是使用 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;
}

Then I can call these functions from a Java class tests.Test:然后我可以从 Java class 测试中调用这些函数。测试:

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));
    }
}

Running the Java main prints the expected result:运行 Java 主打印预期结果:

hello from rust
42

In the Rust methods I declared env as a pointer to the Any type but in reality it is a C struct with pointers to functions as described in the documentation (Code example 4-1) which are required to exchange data with the Java runtime.在 Rust 方法中,我将env声明为指向Any类型的指针,但实际上它是一个 C 结构,其中包含指向文档(代码示例 4-1)中描述的函数的指针,这些函数需要与 ZD52387880E1EA223D 运行时交换数据。

From this answer I understood that such structs with function pointers should have a counterpart in the Rust code to call these functions.从这个答案中,我了解到具有 function 指针的此类结构应该在 Rust 代码中具有对应项来调用这些函数。 So I tried to implement such a struct setting all field values to *mut Any except for the GetVersion field:所以我尝试实现这样一个结构,将所有字段值设置为*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,  
    …

When I call the following function from Java which should call the GetVersion function, the JVM crashes:当我从 Java 调用以下 function 应该调用 GetVersion function 时,Z1C425268E68385D1AB5074C17A94F14Z 崩溃:Z16625A2175EDBAD2E36625A2175EDBAD0E389DAD23

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

How should I call the GetVersion function correctly?我应该如何正确调用 GetVersion function? Note that I am really new to this kind of stuff so please feel free to edit this question if required.请注意,我对这类东西真的很陌生,所以如果需要,请随时编辑这个问题。

Apart from the problem that *mut Any / *const Any are fat pointers, there is also a fact that native JNI functions use double indirection when accessing JNINativeInterface structure: 除了*mut Any / *const Any是胖指针的问题之外,还有一个事实是本机JNI函数在访问JNINativeInterface结构时使用双重间接

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

Here, you can see that JNIEnv is a pointer to JNINativeInterface_ structure which actually contains the fields you presented, and GetVersion accepts a pointer to JNIEnv - that is, it requires a pointer to a pointer to JNINativeInterface_ . 在这里,你可以看到JNIEnv是一个指向JNINativeInterface_结构的指针,它实际上包含你提供的字段,而GetVersion接受一个指向JNIEnv的指针 - 也就是说,它需要一个指向JNINativeInterface_的指针。 This Rust program works on my machine (Rust nightly is used but the same code would work in beta with an external libc crate): 这个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 counterpart: 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();
    }
}

Invocation: 调用:

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

65544 is 0x10008, and indeed, I'm running this under Oracle JVM 1.8. 65544是0x10008,实际上,我在Oracle JVM 1.8下运行它。

I guess you can omit _opaque_data field as JNINativeInterface structure is always passed by pointer, so if you only need several first fields from the structure, you can declare only them and ignore the rest. 我猜你可以省略_opaque_data字段,因为JNINativeInterface结构总是由指针传递,所以如果你只需要结构中的几个第一个字段,你可以只声明它们而忽略其余的字段。

A simpler approach would be to use JnrFFI . 一种更简单的方法是使用JnrFFI The JRuby project heavily uses JnrFFI and it is likely to form the basis for the new Java FFI JEP . JRuby项目大量使用JnrFFI,它很可能构成新的Java FFI JEP的基础。 This basically eliminates writing all the JNI nonsense. 这基本上消除了编写所有JNI废话。 Here is sample code that uses JnrFFI to call a Rust function from Java: 以下是使用JnrFFI从Java调用Rust函数的示例代码

Java Code 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 Code 锈编码

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

Here is the full code 这是完整的代码

An alternative way and the most optimal way to call rust code from java is to use GraalVM.从 java 调用 rust 代码的另一种方法和最佳方法是使用 GraalVM。 In my opinion, it's the best option if performance is the primary key thing in your case.在我看来,如果性能是您的主要关键因素,那么这是最好的选择。

GraalVM is a high-performance runtime that provides significant improvements in application performance and efficiency which is ideal for microservices. GraalVM 是一种高性能运行时,可显着提高应用程序的性能和效率,非常适合微服务。 It is intended for applications created in dynamic languages java and Javascript as well as LLVM-based languages like C C++ and Rust. It is intended for applications created in dynamic languages java and Javascript as well as LLVM-based languages like C C++ and Rust. It breaks down the walls between programming languages and makes shared runtime interoperability possible.它打破了编程语言之间的壁垒,使共享运行时互操作性成为可能。 It can function independently or in conjunction with OpenJDK, Node.js, or Oracle Database.它可以单独使用 function,也可以与 OpenJDK、Node.js 或 Oracle 数据库结合使用。

GraalVM can be used with OpenJDK to accelerate the performance of Java programs using a new just-in-time compilation technology. GraalVM 可以与 OpenJDK 一起使用,以使用新的即时编译技术来加速 Java 程序的性能。 Java's bytecode is converted to machine code by GraalVM. Java 的字节码由 GraalVM 转换为机器码。 This arrangement can be advantageous, especially for other JVM-based languages like Scala, as demonstrated by Twitter running GraalVM in production.这种安排可能是有利的,特别是对于其他基于 JVM 的语言,例如 Scala,正如 Twitter 在生产中运行 GraalVM 所证明的那样。

The GraalVM compiler offers performance benefits for highly abstracted applications because it can frequently do away with expensive object allocations. GraalVM 编译器为高度抽象的应用程序提供了性能优势,因为它经常可以消除昂贵的 object 分配。 Details can be found in this study article .可以在这篇研究文章中找到详细信息。

Lets code: Assuming we have an example that we want to call a function from java to rust with one/two arguments and get the result back to java, the code will look like this. Lets code: Assuming we have an example that we want to call a function from java to rust with one/two arguments and get the result back to java, the code will look like this.

1) Approach-->GraalVM 1) 方法-->GraalVM

i want first to warn you that GraalVm it's a bit tricky to handle and in order to become familiar with it needs time thus if you want simplicity go to the second choice. 我想首先警告你 GraalVm 处理起来有点棘手,为了熟悉它需要时间,因此如果你想要简单 go 到第二选择。

Assuming you have everything set up correctly in your machine we can move on the llvm installation (you can follow this guide here ).假设您在您的机器上正确设置了所有内容,我们可以继续 llvm 安装(您可以在此处遵循本指南)。

The LLVM toolchain can be added to GraalVM on demand with the GraalVM Updater tool可以使用 GraalVM Updater 工具按需将 LLVM 工具链添加到 GraalVM

export LLVM_TOOLCHAIN=$($JAVA_HOME/bin/lli --print-toolchain-path)

The above command will install the LLVM toolchain from the GitHub catalog for GraalVM Community users.上述命令将为 GraalVM 社区用户安装 GitHub 目录中的 LLVM 工具链。

 export LLVM_TOOLCHAIN=$($JAVA_HOME/bin/lli --print-toolchain-path)

Rust Rust

Let's look at rustpart.rs , it is a standard Rust function that takes a number finds its cube root and returns it.让我们看看rustpart.rs ,它是一个标准的 Rust function ,它接受一个数字找到它的立方根并返回它。 But we do have to specify #[no_mangle] annotation and we cannot use any crates as well apparently.但是我们必须指定 #[no_mangle] 注释,而且显然我们也不能使用任何 crate。 Simples functions with primitive args/output seem to work but more complex functions do not work when embedded:具有原始 args/输出的简单函数似乎可以工作,但更复杂的函数在嵌入时不起作用:

    rustc --emit=llvm-bc rustpart.rs

We compile the Rust source to binary code using rustc compiler with the --emit=llvm-bc flag:我们使用带有--emit=llvm-bc标志的 rustc 编译器将 Rust 源代码编译为二进制代码:

 rustc --emit=llvm-bc rustpart.rs

it's important to notice that we will use the.bc generated file from java and not the.rs file重要的是要注意我们将使用从 java 生成的 .bc 文件而不是 .rs 文件

Java Java

Now let's move to the Java code simply add this dependency in your pom.xml现在让我们转到 Java 代码,只需在 pom.xml 中添加此依赖项

package io.example;

import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Source;
import org.graalvm.polyglot.Value;

import java.io.File;
import java.io.IOException;

public class App3 {
    public static void main(String[] args) throws IOException {
        File file=new File("generated.bc");
        Context context = Context.newBuilder().allowAllAccess(true).build();
        Source source = Source.newBuilder("llvm", file).build();
        context.eval(source);
        Value ruspart= context.getBindings("llvm").getMember("cube_root");
        Double cubeRoot = ruspart.execute(10).asDouble();
        System.out.println(cubeRoot);
    }
}

And the java code will look like this: java 代码将如下所示:

 package io.example; import org.graalvm.polyglot.Context; import org.graalvm.polyglot.Source; import org.graalvm.polyglot.Value; import java.io.File; import java.io.IOException; public class App3 { public static void main(String[] args) throws IOException { File file=new File("generated.bc"); Context context = Context.newBuilder().allowAllAccess(true).build(); Source source = Source.newBuilder("llvm", file).build(); context.eval(source); Value ruspart= context.getBindings("llvm").getMember("cube_root"); Double cubeRoot = ruspart.execute(10).asDouble(); System.out.println(cubeRoot); } }

Et Voila瞧瞧

2) Approach-->j4rs 2) 方法-->j4rs

From another point of view if simplicity and flexibility is the primary case you can use as alternative `j4rs`. 从另一个角度来看,如果简单性和灵活性是主要情况,您可以将其用作替代 `j4rs`。 I will dive into deep details. 我将深入细节。

Rust Rust

The rust lib.rs: rust lib.rs:

 use std::convert::TryFrom; use std::result::Result; use j4rs::InvocationArg; use j4rs::prelude::*; use j4rs_derive::*; use serde::Deserialize; #[call_from_java("io.example.RustFunctionCalls.addintegers")] pub extern fn add_integers(integer_instance1: Instance, integer_instance2: Instance) -> Result<Instance, String> { let jvm: Jvm = Jvm::attach_thread().unwrap(); let i1: i32 = jvm.to_rust(integer_instance1).unwrap(); let i2: i32 = jvm.to_rust(integer_instance2).unwrap(); let sum = i1 + i2; let ia = InvocationArg::try_from(sum).map_err(|error| format,("{}". error));unwrap(): Instance:.try_from(ia),map_err(|error| format ("{}" error)) }

The cargo file:货物文件:

<dependency>
    <groupId>io.github.astonbitecode</groupId>
    <artifactId>j4rs</artifactId>
    <version>0.12.0</version>
</dependency>

We execute the following command to produce the.dll library if we are on a windows system:如果我们在 windows 系统上,我们执行以下命令来生成.dll 库:

package io.example;

import org.astonbitecode.j4rs.api.Instance;
import org.astonbitecode.j4rs.api.java2rust.Java2RustUtils;


public class RustFunctionCalls {

    private static native Instance addintegers(Instance<Integer> i1, Instance<Integer> i2);


    public Integer addInRust(Integer i1, Integer i2) {
        Instance instance = addintegers(
                Java2RustUtils.createInstance(i1),
                Java2RustUtils.createInstance(i2));
        return Java2RustUtils.getObjectCasted(instance);
    }
}

After that, we can observe the.dll file created under target/debug path之后,我们可以观察到在target/debug路径下创建的.dll文件

Java Java

We include the following dependency in the pom.xml我们在 pom.xml 中包含以下依赖项

<dependency> <groupId>io.github.astonbitecode</groupId> <artifactId>j4rs</artifactId> <version>0.12.0</version> </dependency>

The java code will look like this here are the RustFunctionCalls java 代码如下所示是 RustFunctionCalls

 package io.example; import org.astonbitecode.j4rs.api.Instance; import org.astonbitecode.j4rs.api.java2rust.Java2RustUtils; public class RustFunctionCalls { private static native Instance addintegers(Instance<Integer> i1, Instance<Integer> i2); public Integer addInRust(Integer i1, Integer i2) { Instance instance = addintegers( Java2RustUtils.createInstance(i1), Java2RustUtils.createInstance(i2)); return Java2RustUtils.getObjectCasted(instance); } }

And here is the main that we call:这是我们所说的主要内容:

 package io.example; import java.io.File; public class App3 { public static void main(String[] args) { File f = new File(App3.class.getResource("/rustlib.dll").getFile()); System.load(f.getAbsolutePath()); RustFunctionCalls rustFnCalls = new RustFunctionCalls(); Integer result=rustFnCalls.addInRust(1,2); System.out.println(result); } }

With this simple example, you can make calls from java to rust通过这个简单的示例,您可以从 java 调用 rust

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM