jni-simple
This crate contains a simple dumb handwritten rust wrapper around the JNI (Java Native Interface) API. It does absolutely no magic around the JNI Calls and lets you just use them as you would in C.
Examples
Loading a JVM on from a shared object file or dll
Note: this example assumes the loadjvm feature is enabled!
use ;
use null;
Writing a JNI shared library that implements a native method
The complete version of this example can be found in the repository inside the example_project folder.
use Write;
use ;
use ;
use ;
use thread;
use Duration;
use stdout;
//Optional: Only needed if you need to spawn "rust" threads that need to interact with the JVM.
extern "system"
pub unsafe extern "system"
//Would be called from java. the signature in java is org.example.JNITest#test()
pub unsafe extern "system"
Main goals of this crate
Dont pretend that JNI is "safe"
JNI is inherently unsafe (from a rust point of view) and any attempt to enforce safety will lead to performance or API complexity issues. All JNI methods provided by this crate are marked as "unsafe" as they should be.
Simple type system of JNI is kept as is
All types like jobject, jstring, jarray,... which are opaque handles represented as pointers in C are represented as raw opaque pointers in Rust that are type aliases of each other. This essentially makes them just hint to the user and doesn't enforce any type safety as that would sometimes be a big hindrance when working with JNI.
Designed for runtime dynamic linking of the JVM
The Problem: The existing jni crate depends on the jni-sys crate which requires the JVM to be resolvable by the dynamic linker. There are 2 ways to do this. The first is to statically link the JVM into the binary, this is rarely done, very cumbersome and poorly documented. The other is to provide the JVM on the linker path so ldd can find it, but I have never seen this occur in the real world either.
This crate is developed for the more common use case that the JVM is available somewhere on the system and leaves it up to the user of the crate to write the necessary code to find and load the JVM.
This allows for maximum flexibility when writing a launcher app which for example may first download a JVM from the internet.
As should be obvious, when writing a native library that does not launch the JVM itself and
is loaded by System.load or System.loadLibrary then this is irrelevant.
Features
loadjvm
This feature provides functions to dynamically link the jvm using the libloading crate
from a string containing the absolute path to libjvm.so or jvm.dll.
Note: If you do not want to use the libloading create but still start the JVM then there are methods provided to
load the JVM from a pointer to JNI_CreateJavaVM function instead of a dll/so file.
Do that if you want to do dynamic linking yourself using dlopen or LoadLibraryA for example.
Note: This feature should not be used when writing a library that is loaded by System.load or System.loadLibrary.
It would just add a dependency that is not needed.
asserts
This feature enables assertions in the code. This is useful for debugging and testing purposes. These checks will cause a big performance hit and should not be used in production builds.
I would not even recommend using this feature for normal debugging builds unless you are specifically debugging issues that occur in relation to the JNI interface. There is no need to enable this feature when you are just debugging a problem that occurs in pure rust.
This feature should NOT be used with the jvm launch option -Xcheck:jni
as the assertions contain calls to env.ExceptionCheck() which will fool the JVM into thinking
that your user code checks for exceptions, which it may not do.
I recommend using this feature before or after you have tested your code with -Xcheck:jni depending
on what problem your troubleshooting. The assertions are generally much better at detecting things like null pointers
or invalid parameters than the JVM checks, while the JVM checks are able to catch missing exception checks or JVM Local Stack overflows better.
Further Info
Variadic up-calls
Currently, variadic up-calls into JVM code are only implemented for 0 to 3 parameters. (Do not confuse this with java "Variadic" methods, for JNI they are just a single parameter that is an Array)
JNI provides 3 ways of up-calling into JVM code:
- CallStatic(TYPE)Method(class, methodID, ...)
- CallStatic(TYPE)MethodA(class, methodID, jtype*)
- CallStatic(TYPE)MethodV(class, methodID, va_list)
Substitute (TYPE) for the return type of the java method called. For example "Object" or "Int".
This crate only implements variant 1 and 2. Variant 3 is not implemented by this crate because "va_list" cannot be created inside rust code.
Variant 2 is relatively straight forward and fully supported by this crate. This means you can call ANY java method using Variant 2.
Variant 1 has a Variadic parameter. This is Variadic up-calls refers to. Rust does support this but only for explicit extern "C" functions and not for any functions implemented in rust itself. To call Variant 1 this crate provides concrete implementations to call this Variadic function with 0, 1, 2 and 3 parameters. This should cover 99% of your up-call needs. To call methods with more than 3 parameters simply use Variant 2.
As you can see calling Variant 2 is a bit unwieldy for so for most smaller functions using Variant 1 of up-calling is probably the better choice. Example:
use null;
use ;
pub unsafe extern "system"