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