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