2024-09-21 17:55:13 +02:00
# jni-toolbox
2024-09-22 02:12:04 +02:00
[![Actions Status ](https://github.com/hexedtech/jni-toolbox/actions/workflows/test.yml/badge.svg )](https://github.com/hexedtech/jni-toolbox/actions)
[![Crates.io Version ](https://img.shields.io/crates/v/jni-toolbox )](https://crates.io/crates/jni-toolbox)
[![docs.rs ](https://img.shields.io/docsrs/jni-toolbox )](https://docs.rs/jni-toolbox)
2024-09-21 17:55:13 +02:00
this is a simple crate built around [jni-rs ](https://github.com/jni-rs/jni-rs ) to automatically generate JNI-compatible extern functions
2024-09-21 17:09:06 +02:00
it also wraps functions returning `Result<>` , making short-circuiting easy
## usage
2024-09-22 01:54:26 +02:00
just specify package and class on your function, and done!
2024-09-21 17:09:06 +02:00
```rust
2024-09-21 17:55:13 +02:00
#[jni_toolbox::jni(package = "your.package.path", class = "ContainerClass")]
2024-09-22 01:54:26 +02:00
fn your_function_name(arg: String) -> Result< Vec < String > , String> {
2024-09-22 01:57:39 +02:00
Ok(arg.split('/').map(|x| x.to_string()).collect())
2024-09-21 17:09:06 +02:00
}
```
2024-09-22 02:03:21 +02:00
### conversions
2024-09-22 01:54:26 +02:00
every type that must go into/from Java must implement `IntoJava` or `FromJava` (methods will receive a `&mut JNIEnv` and can return errors).
most primitives already have them implemented. conversions are automatic and the wrapper function will invoke IntoJava/FromJava for every type,
passing an environment reference.
2024-09-22 02:03:21 +02:00
```rust
impl< 'j> IntoJava for MyClass {
type T = jni::sys::jobject;
fn into_java(self, env: & mut jni::JNIEnv< 'j>) -> Result< Self::T , jni::errors::Error > {
let hello = env.new_string("world")?;
// TODO!!
}
}
```
2024-09-22 01:54:26 +02:00
2024-09-22 02:03:21 +02:00
### pointers
to return pointer type values, add the `ptr` attribute
note that, while possible to pass raw pointers to the JVM, it is not safe by default and must be done with extreme care.
### exceptions
2024-09-22 01:54:26 +02:00
Errors are thrown automatically when a `Result` is an error. For your errors to work, you must implement the `JniToolboxError` trait for your errors,
(which just returns the path to your Java error class) and then make a Java error wrapper which can be constructed with a single string argument.
functions returning `Result` s will automatically have their return value unwrapped and, if is an err, throw an exception and return early.
2024-09-22 02:03:21 +02:00
```rust
impl JniToolboxError for MyError {
fn jclass(& self) -> String {
"my/package/some/MyError".to_string()
}
}
```
```java
package my.package.some;
public class MyError {
public MyError(String x) {
// TODO
}
}
```
2024-09-22 01:54:26 +02:00
to throw simple exceptions, it's possible to use the `exception` attribute. just pass your exception's path (must be constructable with a single string argument!)
2024-09-21 17:09:06 +02:00
2024-09-21 17:36:51 +02:00
2024-09-22 02:03:21 +02:00
### examples
2024-09-21 17:09:06 +02:00
the following function:
```rust
2024-09-22 01:54:26 +02:00
#[jni(package = "mp.code", class = "Client", ptr)]
fn connect(config: Config) -> Result< Client , ConnectionError > {
2024-09-22 01:57:39 +02:00
tokio().block_on(Client::connect(config))
2024-09-21 17:09:06 +02:00
}
```
2024-09-22 01:54:26 +02:00
gets turned into these two functions:
< details > < summary > show macro expansion< / summary >
2024-09-21 17:09:06 +02:00
```rust
2024-09-22 01:54:26 +02:00
fn connect(config: Config) -> Result< Client , ConnectionError > {
2024-09-22 01:57:39 +02:00
tokio().block_on(Client::connect(config))
2024-09-21 17:09:06 +02:00
}
#[no_mangle]
2024-09-22 01:54:26 +02:00
#[allow(unused_mut)]
pub extern "system" fn Java_mp_code_Client_connect< 'local>(
2024-09-22 01:57:39 +02:00
mut env: jni::JNIEnv< 'local>,
_class: jni::objects::JClass< 'local>,
mut config: < Config as jni_toolbox::FromJava < ' local > >::T,
2024-09-22 01:54:26 +02:00
) -> < Client as jni_toolbox::IntoJava < ' local > >::T {
2024-09-22 01:57:39 +02:00
use jni_toolbox::{FromJava, IntoJava, JniToolboxError};
let mut env_copy = unsafe { env.unsafe_clone() };
let config_new = match jni_toolbox::from_java_static::< Config > (& mut env, config) {
Ok(x) => x,
Err(e) => {
let _ = env.throw_new(
"java/lang/RuntimeException",
$crate::__export::must_use({
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
res
}),
);
return std::ptr::null_mut();
}
};
match connect(config_new) {
Err(e) => match env_copy.find_class(e.jclass()) {
Err(e) => {
$crate::panicking::panic_fmt($crate::const_format_args!(
"error throwing Java exception -- failed resolving error class: {e}"
));
}
Ok(class) => match env_copy.new_string($crate::__export::must_use({
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
res
})) {
Err(e) => {
$crate::panicking::panic_fmt($crate::const_format_args!(
"error throwing Java exception -- failed creating error string: {e}"
));
}
Ok(msg) => match env_copy.new_object(
class,
"(Ljava/lang/String;)V",
& [jni::objects::JValueGen::Object(& msg)],
) {
Err(e) => {
$crate::panicking::panic_fmt($crate::const_format_args!(
"error throwing Java exception -- failed creating object: {e}"
));
}
Ok(obj) => match env_copy.throw(jni::objects::JThrowable::from(obj)) {
Err(e) => {
$crate::panicking::panic_fmt($crate::const_format_args!(
"error throwing Java exception -- failed throwing: {e}"
));
}
Ok(_) => return std::ptr::null_mut(),
},
},
},
},
Ok(ret) => match ret.into_java(& mut env_copy) {
Ok(fin) => return fin,
Err(e) => {
let _ = env_copy.throw_new(
"java/lang/RuntimeException",
$crate::__export::must_use({
let res = $crate::fmt::format($crate::__export::format_args!("{e:?}"));
res
}),
);
return std::ptr::null_mut();
}
},
}
2024-09-21 17:09:06 +02:00
}
```
2024-09-22 01:54:26 +02:00
< / details >
2024-09-21 17:09:06 +02:00
## status
this crate is rather early and intended mostly to maintain [`codemp` ](https://github.com/hexedtech/codemp ) java bindings, however it's also quite small and only runs at comptime, so should be rather safe to use