1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228
#![warn(missing_docs)] //! # 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`: //! //! ```java //! 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"); //! } //! //! // The rest is just regular ol' Java! //! public static void main(String[] args) { //! String output = HelloWorld.hello("josh"); //! System.out.println(output); //! } //! } //! ``` //! //! 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, the Java compiler can generate that for you! //! Run `javac -h . HelloWorld` and you'll get a `HelloWorld.h` output to your //! directory. It should look something like this: //! //! ```c //! /* DO NOT EDIT THIS FILE - it is machine generated */ //! #include <jni.h> //! /* Header for class HelloWorld */ //! //! #ifndef _Included_HelloWorld //! #define _Included_HelloWorld //! #ifdef __cplusplus //! extern "C" { //! #endif //! /* //! * Class: HelloWorld //! * Method: hello //! * Signature: (Ljava/lang/String;)Ljava/lang/String; //! */ //! JNIEXPORT jstring JNICALL Java_HelloWorld_hello //! (JNIEnv *, jclass, jstring); //! //! #ifdef __cplusplus //! } //! #endif //! #endif //! ``` //! //! 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]`, add `jni = "0.19.0"` //! * Add a new `[lib]` section and under it, `crate_type = ["cdylib"]`. //! //! Now, if you run `cargo build` from inside the crate directory, you should //! see a `libmylib.so` (if you're on linux) or a `libmylib.dylib` (if you are on 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`: //! //! ```rust,ignore //! // This is the interface to the JVM that we'll call the majority of our //! // methods on. //! use jni::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 jni::objects::{JClass, JString}; //! //! // 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 jni::sys::jstring; //! //! // This keeps Rust from "mangling" the name and making it unique for this //! // crate. //! #[no_mangle] //! pub extern "system" fn Java_HelloWorld_hello(env: JNIEnv, //! // This is the class that owns our static method. It's not going to be used, //! // but still must be present to match the expected signature of a static //! // native method. //! class: JClass, //! input: JString) //! -> jstring { //! // First, we have to get the string out of Java. Check out the `strings` //! // module for more info on how this works. //! let input: String = //! env.get_string(input).expect("Couldn't get java string!").into(); //! //! // Then we have to create a new Java string to return. Again, more info //! // in the `strings` module. //! let output = env.new_string(format!("Hello, {}!", input)) //! .expect("Couldn't create java string!"); //! //! // Finally, extract the raw pointer to return. //! output.into_inner() //! } //! ``` //! //! 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 JVM 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. //! //! ## Launching JVM from Rust //! //! It is possible to launch a JVM from a native process using the [Invocation API], provided //! by [`JavaVM`](struct.JavaVM.html). //! //! ## See Also //! //! ### Examples //! - [Example project][jni-rs-example] //! - Our [integration tests][jni-rs-its] and [benchmarks][jni-rs-benches] //! //! ### JNI Documentation //! - [Java Native Interface Specification][jni-spec] //! - [JNI tips][jni-tips] — general tips on JNI development and some Android-specific //! //! ### Open-Source Users //! - The Servo browser engine Android [port][users-servo] //! - The Exonum framework [Java Binding][users-ejb] //! - MaidSafe [Java Binding][users-maidsafe] //! //! ### Other Projects Simplifying Java and Rust Communication //! - Consider [JNR][projects-jnr] if you just need to use a native library with C interface //! - Watch OpenJDK [Project Panama][projects-panama] which aims to enable using native libraries //! with no JNI code //! - Consider [GraalVM][projects-graalvm] — a recently released VM that gives zero-cost //! interoperability between various languages (including Java and [Rust][graalvm-rust] compiled //! into LLVM-bitcode) //! //! [Invocation API]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/invocation.html //! [jni-spec]: https://docs.oracle.com/en/java/javase/11/docs/specs/jni/index.html //! [jni-tips]: https://developer.android.com/training/articles/perf-jni //! [jni-rs-example]: https://github.com/jni-rs/jni-rs/tree/master/example //! [jni-rs-its]: https://github.com/jni-rs/jni-rs/tree/master/tests //! [jni-rs-benches]: https://github.com/jni-rs/jni-rs/tree/master/benches //! [users-servo]: https://github.com/servo/servo/tree/master/ports/libsimpleservo //! [users-ejb]: https://github.com/exonum/exonum-java-binding/tree/master/exonum-java-binding/core/rust //! [users-maidsafe]: https://github.com/maidsafe/safe_client_libs/tree/master/safe_app_jni //! [projects-jnr]: https://github.com/jnr/jnr-ffi/ //! [projects-graalvm]: http://www.graalvm.org/docs/why-graal/#for-java-programs //! [graalvm-rust]: http://www.graalvm.org/docs/reference-manual/languages/llvm/#running-rust //! [projects-panama]: https://jdk.java.net/panama/ /// `jni-sys` re-exports pub mod sys; mod wrapper { mod version; pub use self::version::*; #[macro_use] mod macros; /// Errors. Do you really need more explanation? pub mod errors; /// Descriptors for classes and method IDs. pub mod descriptors; /// Parser for java type signatures. pub mod signature; /// Wrappers for object pointers returned from the JVM. pub mod objects; /// String types for going to/from java strings. pub mod strings; /// Actual communication with the JVM. mod jnienv; pub use self::jnienv::*; /// Java VM interface. mod java_vm; pub use self::java_vm::*; /// Optional thread attachment manager. mod executor; pub use self::executor::*; } pub use wrapper::*;