Version 0.21 makes extensive breaking changes in order to improve safety. Most projects that use this library will need to be changed accordingly.
This is a guide for migrating to 0.21. For a full list of changes in this release, please see the changelog.
Most of these changes are needed to ensure that all local references (JObject
and the like) have the correct lifetime and can‘t be used after they’re deleted, which would cause undefined behavior. See issue #392 for a discussion of the problem these changes solve.
JNIEnv
parameter of extern fn
s should now be mut
In extern fn
s that are directly called by the JVM, the JNIEnv
parameter will usually need to be mut
.
#[no_mangle] pub extern "system" fn Java_HelloWorld_hello<'local>(mut env: JNIEnv<'local>, class: JClass<'local>, input: JString<'local>) -> jstring { … }
This is needed because most JNIEnv
methods now take a &mut self
parameter.
JNIEnv
, JObject
, etc are now !Copy
and should be borrowedFunctions that are not directly called by the JVM should, in most cases, borrow local references (JObject
, JString
, and so on) and mutably borrow JNIEnv
s.
pub fn print_string(env: &mut JNIEnv, string: &JString) -> Result<()> { println!("{}", env.get_string(string)?.to_string_lossy()); Ok(()) }
This is needed because these types no longer have the Copy
or Clone
traits.
JNIEnv::with_local_frame
closure now takes a &mut JNIEnv
parameterWhen using JNIEnv::with_local_frame
, Executor::with_attached
, or Executor::with_attached_capacity
, the closure must now take a parameter of type &mut JNIEnv
.
env.with_local_frame(16, |env| { … })
The closure must only use the JNIEnv
passed to it in that parameter, and not the JNIEnv
that with_local_frame
was called on.
JNIEnv::with_local_frame
closure can return a generic Result
Note: This also applies to Executor::with_attached
and Executor::with_attached_capacity
which are thin wrappers over with_local_frame
The closure passed to with_local_frame
is now free to return a generic Result
as long as the error type implements From<jni::errors::Error>
.
This can be particularly beneficial when running large amounts of code within a local frame in a crate that defines its own Result
and Error
types which need to be propagated to the caller.
There are a few trade offs with this change though:
Ok(())
) and so it has to be explicitly specifiedTwo options for clarifying the error type for the compiler if it's ambiguous would be:
let result: MyResult<()> = env.with_local_frame(10, |env| { Ok(()) });
E
error parameter:env.with_local_frame::<_, _, MyError>(10, |env| { Ok(()) })?;
Code that returns a local reference to the calling frame can either use JNIEnv::with_local_frame_returning_local
(which is marginally optimized for that special case) or else return a GlobalRef
instead. (This approach works reasonably well in Rust, compared to C, because a global reference will be automatically deleted once it is dropped so it doesn't introduce a memory leak hazard like it would in C)
When passing an object reference as a parameter to a Java method or constructor, it needs to be explicitly borrowed, as in (&obj).into()
, instead of simply obj.into()
.
env.call_static_method( "com/example/SomeClass", "someStaticMethod", "(Ljava/lang/Object;)V", &[ (&obj).into(), ], )
JList
and JMap
methods all take a &mut JNIEnv
parameterAll methods of JList
and JMap
now require a parameter of type &mut JNIEnv
. They no longer store the JNIEnv
that was used to construct them.
This is needed because most JNIEnv
methods now take a &mut self
parameter, and if the JList
or JMap
did store a &mut JNIEnv
, then the caller would be unable to use the JNIEnv
for anything else without first dropping the JList
or JMap
.
JList
and JMap
iterators no longer implement Iterator
Just like the methods of JList
and JMap
, their iterator now also requires a &mut JNIEnv
in order to get the next element.
For this reason, the iterator returned by JList::iter
and JMap::iter
no longer implements std::iter::Iterator
and can no longer be used with a for
loop. Instead, it has a next
method that takes a &mut JNIEnv
parameter, and can be used with a while let
loop.
let mut iterator = list.iter(env)?; while let Some(obj) = iterator.next(env)? { let obj: AutoLocal<JObject> = env.auto_local(obj); // Do something with `obj` here. }
JNIEnv
methods that look up Java classes no longer leak local references (#109), so it is no longer necessary to wrap calls to these methods inside JNIEnv::with_local_frame
.
cannot borrow as mutable
If your project has a function that takes two or more parameters, one of them is JNIEnv
, and another is something returned by a JNIEnv
method (like JObject
), then calls to that function may not compile.
fn example_function( env: &mut JNIEnv, obj: &JObject, ) { // … } example_function( env, // ERROR: cannot borrow `*env` as mutable more than once at a time &env.new_object( "com/example/SomeClass", "()V", &[], )?, )
To fix this, the JNIEnv
parameter needs to come last.
fn example_function( obj: &JObject, env: &mut JNIEnv, ) { // … } example_function( &env.new_object( "com/example/SomeClass", "()V", &[], )?, env, );
invocation
feature now finds and loads the JVM dynamically; JavaVM::new
returns different errorThe invocation
feature has been changed to locate and load the Java Virtual Machine at run time, using the java-locator and libloading libraries.
JavaVM::new
now returns a different error type, StartJvmError
, when it fails. This new error type covers failures to locate and load the JVM library as well as failures to initialize the JVM.
If you need to load a specific JVM library instead of automatically discovering one, use JavaVM::with_libjvm
instead.
On non-Windows platforms, it is no longer necessary for the Java runtime‘s lib
folder to be on the shared library search path (LD_LIBRARY_PATH
or equivalent). Unfortunately, this is not true on Windows, where the Java runtime’s DLLs must still be on the PATH
(or added to the DLL search path with SetDllDirectory
).
Desc
has been redesignedThe Desc
trait has been redesigned. If your project contains any implementations of Desc
and/or calls to Desc::lookup
, they will need to be updated.
If your project uses Desc
directly like this, please post a comment on issue #403 explaining your situation. We view Desc
as an implementation detail, and are considering sealing it and hiding its contents in a future release.
Changes to Desc
in this release are as follows:
Desc
is now an unsafe trait
. The safety requirements haven't actually changed, but they were undocumented until now.
Desc
now has an associated type, Output
, which is now the type returned by the lookup
method.
Please see the documentation for the Desc
trait for more information.
JNIEnv::call_*method_unchecked
is now unsafe
The JNIEnv::call_*method_unchecked
methods are now unsafe
. Their safety requirements haven't changed, but they were undocumented until now.
AutoLocal
type and lifetime parameters changedAutoLocal
now has one lifetime parameter, representing the local reference frame it belongs to, and one type parameter, representing the type of object reference it contains (JObject
, JClass
, and so on).
This means that the object reference stored in an AutoLocal
no longer has its type erased. If it was a JClass
before wrapping it in AutoLocal
, it will still be a JClass
after.
// `AutoLocal` type parameters now include the type of object reference… let auto_local: AutoLocal<'local, JClass<'local>>; // …so the type of the object reference is now kept instead of being erased. let local: &JClass<'local> = &*auto_local;
JObject
no longer implements From<&AutoLocal> + From<&GlobalRef>
It is no longer possible to directly convert an &AutoLocal
or &GlobalRef
into a JObject
. Instead, a JObject
can be borrowed from AutoLocal
or GlobalRef
through their implementations of Deref
and/or AsRef<JObject>
.
let global_ref: GlobalRef; // You can get a `JObject` from a `GlobalRef` two ways: let object_ref: &JObject<'static> = &*global_ref; let object_ref: &JObject<'static> = global_ref.as_ref();
JNIEnv::get_superclass
now returns Option
The JNIEnv::get_superclass
method previously returned a JClass
, which would be null if the class in question doesn‘t have a superclass. It now returns Option<JClass>
instead, and when it’s Some
, the JClass
inside is never null.
JNIEnv::{get,release}_string_utf_chars
removedThe methods JNIEnv::get_string_utf_chars
and JNIEnv::release_string_utf_chars
have been removed. These methods have been replaced with JavaStr::into_raw
and JavaStr::from_raw
. To get a JavaStr
, use JNIEnv::get_string
or JNIEnv::get_string_unchecked
. See issue #372 for discussion.
JPrimitiveArray<T>
and JObjectArray
provide safe reference wrappers (like JObject
) for jarray
This affects all JNIEnv
array APIs including:
new_<type>_array
get_array_length
get_object_array_element
set_object_array_element
get_<type>_array_region
set_<type>_array_region
get_array_elements
get_<type>_array_elements
(all removed)get_primitive_array_elements
(also renamed to get_array_elements_critical
)With the exception of get_array_length
these APIs now take or return a reference to a JPrimitiveArray
or a JObjectArray
instead of a sys
type like jarray
or jbyteArray
. This improves safety since the sys types can be copied and easily read as invalid pointers after a reference has been deleted.
get_array_length
will accept a reference to a JPrimitiveArray
or a JObjectArray
since these both implement the AsJArrayRaw
trait. You can also query the .len()
of an array via the AutoElements
/AutoElementsCritical
APIs if you use those.
There are the following type-specific aliases for JPrimitiveArray<T>
:
JBooleanArray
JByteArray
JCharArray
JShortArray
JIntArray
JLongArray
JFloatArray
JDoubleArray
AutoArray
and AutoPrimitiveArray
renamed to AutoElements
and AutoElementsCritical
respectivelyThis rename was done to:
AutoElementsCritical
is an alternative that defines a restricted “critical” section that helps JNI avoid the need to copy the data for the array)JPrimitiveArray
/JObjectArray
types and aliases like JByteArray
that represent the array itself (not the elements)AutoElements<T>
and AutoElementsCritical<T>
now implement Deref<Target=[T]>
and DerefMut
Previously accessing array elements required the use of unsafe
code to access array elements via AutoArray::as_ptr()
after acquiring an AutoArray
guard.
It's now possible to read and write elements via the Deref
and DerefMut
traits that will deref into a [T]
slice like:
let byte_array = env.new_byte_array(100)?; { let mut elements = unsafe { env.get_array_elements(&byte_array, ReleaseMode::CopyBack) }?; elements[0] = 0xff; assert_eq!(elements[0], 0xff); // elements released (copied back to Java) here on Drop }
If you are accessing a JPrimitiveArray<jbyte>
/JByteArray
you may find it helpful to utilize the bytemuck crate in case you need to access the elements as u8
unsigned bytes instead of i8
.
Although this hasn't changed, it seems worth highlighting that if you are accessing a JPrimitiveArray<jboolean>
/JBooleanArray
you are responsible for only storing values of 0
or 1
, since other values could lead to undefined behaviour for the JVM.
AutoArray/AutoPrimitiveArray::size()
replace by AutoElements/AutoElementsCritical::len()
Previously AutoArray::size()
was a wrapper for calling JNIEnv::get_array_length()
which could fail, where as AutoElements
and AutoElementsCritical
now need to know the array length to be constructed, and this constant is accessible via the .len()
method.
This change was required to support being able to Deref
to a [T]
slice.
get_array_elements[_critical]
are unsafe
Previously get_array_elements
and get_primitive_array_critical
could be called by safe code but there were multiple ways in which these APIs could quite easily lead to undefined behaviour.
Since it is only possible to acquire an AutoElements
and AutoElementsCritical
instance via get_array_elements
and get_primitive_array_critical
, that's where we now document the safety rules that need to be followed to avoid any undefined behaviour.