Crate robusta_jni
source · [−]Expand description
robusta_jni
is a library that provides a procedural macro to make easier to write JNI-compatible code in Rust.
It can perform automatic conversion of Rust-y input and output types.
[dependencies]
robusta_jni = "0.2"
Getting started
The #[bridge]
attribute is robusta_jni
’s entry point. It must be applied to a module.
robusta_jni
will then generate proper function definitions and trait implementations depending on declared methods.
Declaring classes
Rust counterparts of Java classes are declared as Rust struct
s, with a #[package(my.package.name)]
attribute.
When using the default package, just omit the package name inside parentheses.
Structs without the package attribute will be ignored by robusta_jni
.
In order to use the features of robusta_jni
, declared structs should also implement the Signature
trait.
This can be done manually or with autoderive.
Example:
use robusta_jni::bridge;
use robusta_jni::convert::Signature;
#[bridge]
mod jni {
#[package()] // default package
struct A;
impl Signature for A {
const SIG_TYPE: &'static str = "LA;";
}
#[derive(Signature)]
#[package(my.awesome.package)]
struct B;
}
Adding native methods
JNI bindings are generated for every method implemented for package
-annotated structs.
Each method can optionally specify a #[call_type]
attribute that will determine how conversions between Rust and Java types are performed.
For more information about conversions and #[call_type]
, check out the convert module.
In general, all input and output types must implement proper conversion traits
(input types must implement (Try)FromJavaValue
and output types must implement (Try)IntoJavaValue
)
Native methods can optionally accept a JNIEnv
parameter as first parameter (after self
if present).
Methods are declared as standard Rust functions with public visibility and “jni” ABI, and are matched by name with Java methods. No special handling is needed.
Example:
impl A {
pub extern "jni" fn op(self, _env: &JNIEnv, flag: bool) -> i32 {
// ^^^^^ optional
if flag {
1
} else {
0
}
}
// here the `env` parameter is omitted
pub extern "jni" fn special(mut input1: Vec<i32>, input2: i32) -> Vec<String> {
input1.push(input2);
input1.iter().map(ToString::to_string).collect()
}
}
Adding Java methods
You can also declare Java methods and robusta
will generate binding glue to convert types and call methods on the Java side.
Again, all input and output types must implement proper conversion traits: in this case it’s the reverse from the Java to Rust case
(input types must implement (Try)IntoJavaValue
and output types must implement (Try)FromJavaValue
).
Methods are declared as standard Rust functions with public visibility, a “java” ABI and an empty body, and are matched by name with Java methods.
Both static and non-static methods must accept a JNIEnv
parameter as first parameter (after self if present).
Constructors can be declared via a #[constructor]
attribute on static methods, and are matched by their type signature.
When using #[call_type(safe)]
or omitting call_type
attribute, the output type must be jni::errors::Result<T>
with T
being the actual method return type. Otherwise when using #[call_type(unchecked)]
T
is sufficient.
When using #[call_type(unchecked)]
if a Java exception is thrown while calling a method a panic is raised.
Static methods
Example:
impl A {
pub extern "java" fn staticJavaMethod(
env: &JNIEnv,
i: i32,
u: i32,
) -> ::robusta_jni::jni::errors::Result<i32> {}
}
Non-static methods
Example:
impl A {
pub extern "java" fn selfMethod(
&self,
env: &JNIEnv,
i: i32,
u: i32,
) -> ::robusta_jni::jni::errors::Result<i32> {}
}
Constructors
Example:
impl A {
#[constructor] // vvv ---- this method name can be anything because it's a constructor
pub extern "java" fn new(
env: &JNIEnv
) -> ::robusta_jni::jni::errors::Result<Self> {}
}
Conversion details and special lifetimes
The procedural macro handles two special lifetimes specially: 'env
and 'borrow
.
When declaring structs with lifetimes you may be asked to name one of the lifetimes as 'env
in order to
disambiguate code generation for the attribute macro.
In the generated code, this lifetime would correspond to the one used to convert your type to *IntoJavaValue
, like:
<A<'env> as TryIntoJavaValue<'env>>
This lifetime is always used as the lifetime parameter of JNIEnv
instances.
When using *FromJavaValue
derive macros your structs will be required to have both 'env
and 'borrow
,
with the same bounds as in the trait definition. For more information, see the relevant traits documentation.
Raising exceptions
You can make a Rust native method raise a Java exception simply by returning a jni::errors::Result
with an Err
variant.
See the convert
module documentation for more information.
Library-provided conversions
Rust | Java |
---|---|
i32 | int |
bool | boolean |
char | char |
i8 | byte |
f32 | float |
f64 | double |
i64 | long |
i16 | short |
String | String |
Vec<T>† | ArrayList<T> |
jni::JObject<’env> ‡ | (any Java object as input type) |
jni::jobject | (any Java object as output) |
† Type parameter T
must implement proper conversion types
‡ The special 'env
lifetime must be used
Limitations
Currently there are some limitations in the conversion mechanism:
- Boxed types are supported only through the opaque
JObject
/jobject
types - Automatic type conversion is limited to the table outlined above, though easily extendable if needed.
Re-exports
pub use jni;
Modules
Conversion facilities. This module provides two trait families: FromJavaValue/IntoJavaValue (infallible conversions) and TryFromJavaValue/TryIntoJavaValue (fallible conversions), similar to the ones found in the standard library.
Macros
Asserts that all types in a list are equal to each other.