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