Safe JNI Bindings in Rust
This crate provides a (mostly) safe way to implement methods in Java using the JNI. Because who wants to actually write Java?
Getting Started
Naturally, any ffi-related project is going to require some code in both languages that we're trying to make communicate. Java requires all native methods to adhere to the Java Native Interface (JNI), so we first have to define our function signature from java, and then we can write Rust that will adhere to it.
The Java side
First, you need a Java class definition. HelloWorld.java
:
Compile this to a class file with javac HelloWorld.java
.
Trying to run it now will give us the error Exception in thread "main" java.lang.UnsatisfiedLinkError: no mylib in java.library.path
since we haven't
written our native code yet.
To do that, first we need the name and type signature that our rust function
needs to adhere to. Luckily, java comes with a utility to generate that for you!
Run javah HelloWorld
and you'll get a HelloWorld.h
output to your directory.
It should look something like this:
/* DO NOT EDIT THIS FILE - it is machine generated */
/* Header for class HelloWorld */
extern "C" __cplusplus
}
It's a C header, but luckily for us, the types will mostly match up. Let's make our crate that's going to compile to our native library.
The Rust side
Create your crate with cargo new mylib
. This will create a directory mylib
that has everything needed to build an basic crate with cargo
. We need to make a couple of changes to Cargo.toml
before we do anything else.
- Under
[dependencies]
, addjni = { git = "https://github.com/prevoty/jni-rs" }
- Add a new
[lib]
section and under it,crate_type = ["dylib"]
.
Now, if you run cargo build
from inside the crate directory, you should see a
libmylib.so
(if you're on linux/OSX) in the target/debug
directory.
The last thing we need to do is to define our exported method. Add this to your crate's src/lib.rs
:
extern crate jni;
// This is the interface to the JVM that we'll call the majority of our methods on.
use JNIEnv;
// These objects are what you should use as arguments to your native function.
// They carry extra lifetime information to prevent them escaping this context
// and getting used after being GC'd.
use ;
// This is just a pointer. We'll be returning it from our function.
// We can't return one of the objects with lifetime information because the
// lifetime checker won't let us.
use jstring;
// This keeps rust from "mangling" the name and making it unique for this crate.
// This turns off linter warnings because the name doesn't conform to conventions.
pub extern "C"
Note that the type signature for our function is almost identical to the one from the generated header, aside from our lifetime-carrying arguments.
Final steps
That's it! Build your crate and try to run your java class again.
... Same error as before you say? Well that's because java is looking for
mylib
in all the wrong places. This will differ by platform thanks to
different linker/loader semantics, but on Linux, you can simply export LD_LIBRARY_PATH=/path/to/mylib/target/debug
. Now, you should get the
expected output Hello, josh!
from your java class.