jni-toolbox/README.md

161 lines
5.2 KiB
Markdown
Raw Normal View History

2024-09-21 17:55:13 +02:00
# jni-toolbox
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-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