Linking native functions into the JVM
Using the #[java_function]
decorator you can write Rust implementations for Java native functions. To get the JVM to invoke these methods, it has to know how to find them. The way you do this depends on whether you have the "top-level" program is written in Rust or in Java.
Rust program that creates a JVM
If your Rust program is launching the JVM, then you can configure that JVM to link to your native method definitions through methods on the JVM builder.
use duchess::prelude::*; // 👈 You'll need this.
#[java_function(...)]
fn foo(...) { }
fn main() -> duchess::Result<()> {
Jvm::builder()
.link(foo::java_fn()) // 👈 Note the `::java_fn()`.
.try_launch()?;
}
How it works. The call foo::java_fn()
returns a duchess::JavaFunction
struct. The java_fn
method is defined in the duchess JavaFn
trait; that trait is implemented on a struct type foo
that is created by the #[java_function]
decorator. This trait is in the duchess prelude, which is why you need to use duchess::prelude::*
.
Java function suites
Invoking the link method for every java functon you wish to implement is tedious and error-prone. If you have java functions spread across crates and modules, it also presents a maintenance hazard, since each time you add a new #[java_function]
you would also have to remember to add it to the Jvm builder invocation, which is likely located in some other part of the code.
To avoid this, you can create suites of java functions. The idea is that the link
method accepts both individual JavaFunction
structs but also Vec<JavaFunction>
suites. You can then write a function in your module that returns a Vec<JavaFunction>
with all the java functions defined locally:
use duchess::prelude::*;
#[java_function(...)]
fn foo(...) { }
#[java_function(...)]
fn bar(...) { }
fn java_functions() -> Vec<JavaFunction> {
vec![
foo::java_fn(),
bar::java_fn(),
]
}
You can also compose suites from other crates or modules:
fn java_functions() -> Vec<duchess::JavaFunction> {
crate_a::java_functions()
.into_iter()
.chain(crate_b::java_functions())
.collect()
}
And finally you can invoke link()
to link them all at once:
fn main() -> duchess::Result<()> {
Jvm::builder()
.link(java_functions())
.try_launch()?;
}
JVM that calls into Rust
If the JVM is the "master process", then you have to use a different method to link into Rust. First, you have to compile your Rust binary as a cdylib by configuring Cargo.toml
with a new [lib]
section:
[lib]
crate_type = ["cdylib"]
Then in your Java code you have to invoke System.loadLibrary
. Typically you do this in a static
section on the class with the native
method:
class HelloWorld {
// This declares that the static `hello` method will be provided
// a native library.
private static native String hello(String input);
static {
// This actually loads the shared object that we'll be creating.
// The actual location of the .so or .dll may differ based on your
// platform.
System.loadLibrary("mylib");
}
}
Finally, you need to run cargo build
and put the dylib that is produced into the right place. The details different by platform. On Linux, you can export LD_LIBRARY_PATH=/path/to/mylib/target/debug
to link the dylib directly from the Cargo build directory.
These instructions were based on the excellent docs from the jni crate; you can read more there.