• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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