jni-toolbox/README.md

161 lines
5.5 KiB
Markdown
Raw Normal View History

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-23 00:50:52 +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-22 02:12:04 +02:00
2024-09-23 00:50:52 +02:00
It also wraps functions returning `Result<>`, making short-circuiting easy.
2024-09-21 17:09:06 +02:00
2024-09-23 00:50:52 +02:00
## Usage
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-23 00:50:52 +02:00
### Conversions
Every type that is meant to be sent to Java must implement `IntoJavaObject` (or, unlikely, `IntoJavaPrimitive`); every type that is meant to be
received from Java must implement `FromJava`. Most primitives and a few common types should already be implemented.
2024-09-22 01:54:26 +02:00
2024-09-22 02:03:21 +02:00
```rust
2024-09-23 00:50:52 +02:00
impl<'j> IntoJavaObject for MyClass {
type T = jni::objects::JObject<'j>
2024-09-22 02:03:21 +02:00
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-23 00:50:52 +02:00
### Pointers
To return pointer type values, add the `ptr` attribute.
2024-09-22 02:03:21 +02:00
2024-09-23 00:50:52 +02:00
Note that, while it is possible to pass raw pointers to the JVM, it is not safe by default and must be done with extreme care.
2024-09-22 02:03:21 +02:00
2024-09-23 00:50:52 +02:00
### 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.
2024-09-23 00:50:52 +02:00
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 01:54:26 +02:00
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;
2024-09-23 00:50:52 +02:00
public class MyError extends Throwable {
2024-09-22 02:03:21 +02:00
public MyError(String x) {
// TODO
}
}
```
2024-09-23 00:50:52 +02:00
To throw simple exceptions, it's possible to use the `exception` attribute. Pass the exception's fully qualified name (must have a constructor
that takes in a single `String` argument).
2024-09-21 17:09:06 +02:00
2024-09-23 00:50:52 +02:00
### Examples
The following function:
2024-09-21 17:09:06 +02:00
```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-23 00:50:52 +02:00
generates a matching expanded function invoking it:
2024-09-22 01:54:26 +02:00
2024-09-23 00:50:52 +02:00
<details><summary>Show macro expansion</summary>
2024-09-22 01:54:26 +02:00
2024-09-21 17:09:06 +02:00
```rust
#[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
2024-09-23 00:50:52 +02:00
## Status
This crate is early and intended mostly to maintain [`codemp`](https://github.com/hexedtech/codemp)'s Java bindings, so things not used
there may be missing or slightly broken. However, the crate is also quite small and only runs at compile time, so trying it out in your
own project should not be a problem.