blob: 63df2e712d06de627085949d49df55ace4356861 [file] [log] [blame] [view] [edit]
# Migrating to 0.21
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](../CHANGELOG.md).
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](https://github.com/jni-rs/jni-rs/issues/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`.
```rust
#[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 borrowed
Functions 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.
```rust
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` parameter
When using `JNIEnv::with_local_frame`, `Executor::with_attached`, or `Executor::with_attached_capacity`, the closure must now take a parameter of type `&mut JNIEnv`.
```rust
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:
1. Sometimes the compiler won't be able to infer the generic error type (E.g. for code that simply returns `Ok(())`) and so it has to be explicitly specified
2. Since it's no longer assumed that code always wants to return a single local reference this special case has to be handled differently
Two options for clarifying the error type for the compiler if it's ambiguous would be:
1. Specify the type of the return value as part of an assignment:
```rust
let result: MyResult<()> = env.with_local_frame(10, |env| { Ok(()) });
```
2. Specify the generic `E` error parameter:
```rust
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)
## Passing object reference parameters to Java methods
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()`.
```rust
env.call_static_method(
"com/example/SomeClass",
"someStaticMethod",
"(Ljava/lang/Object;)V",
&[
(&obj).into(),
],
)
```
## `JList` and `JMap` methods all take a `&mut JNIEnv` parameter
All 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.
```rust
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.
}
```
## Local references no longer leak
`JNIEnv` methods that look up Java classes no longer leak local references ([#109](https://github.com/jni-rs/jni-rs/issues/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.
```rust
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*.
```rust
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 error
The `invocation` feature has been changed to locate and load the Java Virtual Machine at run time, using the [java-locator](https://crates.io/crates/java-locator) and [libloading](https://crates.io/crates/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`](https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-setdlldirectoryw)).
## `Desc` has been redesigned
The `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](https://github.com/jni-rs/jni-rs/issues/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 changed
`AutoLocal` 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.
```rust
// `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>`.
```rust
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` removed
The 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](https://github.com/jni-rs/jni-rs/pull/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` respectively
This rename was done to:
1. Clarify the connection between these APIs (they both provide temporary access to the elements of an array)
2. Clarify their differences (`AutoElementsCritical` is an alternative that defines a restricted "critical" section that helps JNI avoid the need to copy the data for the array)
3. Differentiate these from the new `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:
```rust
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](https://crates.io/crates/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.