327 lines
14 KiB
Markdown
327 lines
14 KiB
Markdown
# 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.
|