easily write JNI extern functions with native Rust types
Find a file
2024-09-24 01:58:10 +02:00
.github/workflows test: add simple tests on java side 2024-09-24 01:58:10 +02:00
macro feat: simplify IntoJava family 2024-09-23 17:59:31 +02:00
src test: add simple tests on java side 2024-09-24 01:58:10 +02:00
.editorconfig chore: setup project 2024-09-21 17:00:54 +02:00
.gitignore test: add simple tests on java side 2024-09-24 01:58:10 +02:00
.rustfmt.toml chore: setup project 2024-09-21 17:00:54 +02:00
build.gradle test: add simple tests on java side 2024-09-24 01:58:10 +02:00
Cargo.lock test: add simple tests on java side 2024-09-24 01:58:10 +02:00
Cargo.toml test: add simple tests on java side 2024-09-24 01:58:10 +02:00
LICENSE chore: add license file 2024-09-21 17:55:37 +02:00
README.md docs: updated readme 2024-09-23 18:02:27 +02:00

jni-toolbox

Actions Status Crates.io Version docs.rs

This is a simple crate built around jni-rs to automatically generate JNI-compatible extern functions.

It also wraps functions returning Result<>, making short-circuiting easy.

Usage

Just specify package and class on your function, and done!

#[jni_toolbox::jni(package = "your.package.path", class = "ContainerClass")]
fn your_function_name(arg: String) -> Result<Vec<String>, String> {
  Ok(arg.split('/').map(|x| x.to_string()).collect())
}

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.

impl<'j> IntoJavaObject for MyClass {
  type T = jni::objects::JObject<'j>
  fn into_java(self, env: &mut jni::JNIEnv<'j>) -> Result<Self::T, jni::errors::Error> {
    let hello = env.new_string("world")?;
    // TODO!!
  }
}

Pointers

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.

Exceptions

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 Results will automatically have their return value unwrapped and, if is an err, throw an exception and return early.

impl JniToolboxError for MyError {
  fn jclass(&self) -> String {
    "my/package/some/MyError".to_string()
  }
}
package my.package.some;
public class MyError extends Throwable {
  public MyError(String x) {
    // TODO
  }
}

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

Examples

The following function:

#[jni(package = "mp.code", class = "Client", ptr)]
fn connect(config: Config) -> Result<Client, ConnectionError> {
  super::tokio().block_on(Client::connect(config))
}

generates a matching expanded function invoking it:

Show macro expansion
#[doc = " Connect using the given credentials to the default server, and return a [Client] to interact with it."]
fn connect(config: Config) -> Result<Client, ConnectionError> {
  super::tokio().block_on(Client::connect(config))
}
#[no_mangle]
#[allow(unused_unit)]
pub extern "system" fn Java_mp_code_Client_connect<'local>(
  mut env: jni::JNIEnv<'local>,
  _class: jni::objects::JClass<'local>,
  config: <Config as jni_toolbox::FromJava<'local>>::From,
) -> <Client as jni_toolbox::IntoJava<'local>>::Ret {
  use jni_toolbox::{FromJava, IntoJava, JniToolboxError};
  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();
    }
  };
  let mut env_copy = unsafe { env.unsafe_clone() };
  let result = connect(config_new);
  let ret = match result {
    Ok(x) => x,
    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(),
          },
        },
      },
    },
  };
  match ret.into_java(&mut env_copy) {
    Ok(fin) => 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
        }),
      );
      std::ptr::null_mut()
    }
  }
}

Status

This crate is early and intended mostly to maintain 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.