| # 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. |