Reputation: 5062
I am using Rust 1.0 beta and was able to create a small example for calling functions written in Rust from Java. I simply compiled the following Rust code in mylib.rs using rustc which produces a mylib.dll on Windows:
#![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:
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:
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.
From this answer I understood that such structs with function pointers should have a counterpart in the Rust code to call these functions. So I tried to implement such a struct setting all field values to *mut Any
except for the GetVersion
field:
#[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:
#[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? Note that I am really new to this kind of stuff so please feel free to edit this question if required.
Upvotes: 51
Views: 23137
Reputation: 3204
An alternative way and the most optimal way to call rust code from java is to use 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. 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.
GraalVM can be used with OpenJDK to accelerate the performance of Java programs using a new just-in-time compilation technology. Java's bytecode is converted to machine code by GraalVM. This arrangement can be advantageous, especially for other JVM-based languages like Scala, as demonstrated by Twitter running GraalVM in production.
The GraalVM compiler offers performance benefits for highly abstracted applications because it can frequently do away with expensive object allocations. 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.
Assuming you have everything set up correctly in your machine we can move on the llvm installation (you can follow this guide here).
The LLVM toolchain can be added to GraalVM on demand with the GraalVM Updater tool
$GRAALVM_HOME/bin/gu install llvm-toolchain
The above command will install the LLVM toolchain from the GitHub catalog for GraalVM Community users.
export LLVM_TOOLCHAIN=$($JAVA_HOME/bin/lli --print-toolchain-path)
Rust
Let’s look at rustpart.rs
, it is a standard Rust function that takes a number finds its cube root and returns it. But we do have to specify #[no_mangle] annotation and we cannot use any crates as well apparently. Simples functions with primitive args/output seem to work but more complex functions do not work when embedded:
#[no_mangle]
fn cube_root(arg: f64) -> f64 {
arg.cbrt()
}
fn main(){}
We compile the Rust source to binary code using rustc compiler with the --emit=llvm-bc
flag:
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
Now let's move to the Java code simply add this dependency in your pom.xml
<dependency>
<groupId>org.graalvm.sdk</groupId>
<artifactId>graal-sdk</artifactId>
<version>22.2.0</version>
</dependency>
And the java code will look like this:
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!!!!
Rust
The 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:
[package]
name = "rustlib"
version = "0.1.0"
edition = "2018"
[[bin]]
name = "lib"
path = "src/main.rs"
[build]
rustflags = ["-C", "target-cpu=native"]
[lib]
name = "rustlib"
path = "src/lib.rs"
crate-type = ["cdylib"]
[dependencies]
j4rs = "0.12"
j4rs_derive = "0.1"
serde = { version = "1.0", features = ["derive"] }
We execute the following command to produce the.dll library if we are on a windows system:
cargo build --lib
After that, we can observe the .dll file created under target/debug
path
Java
We include the following dependency in the 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
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
Upvotes: 6
Reputation: 1935
A simpler approach would be to use 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 that uses JnrFFI to call a Rust function from Java:
Java Code
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
Upvotes: 22
Reputation: 127711
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:
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_
. This Rust program works on my machine (Rust nightly is used but the same code would work in beta with an external 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:
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.
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.
Upvotes: 24