diff --git a/README.md b/README.md index d15489b..dcde4f0 100644 --- a/README.md +++ b/README.md @@ -4,55 +4,125 @@ this is a simple crate built around [jni-rs](https://github.com/jni-rs/jni-rs) t it also wraps functions returning `Result<>`, making short-circuiting easy ## usage -you must implement `JniToolboxError` trait for your errors, so that they can be converted to Java errors - -you will need to define classes for them, and implement `JniToolboxError` returning the class path - -alternatively, an `exception` class can be specified with the `exception` attribute - -then just annotate your functions with +just specify package and class on your function, and done! ```rust #[jni_toolbox::jni(package = "your.package.path", class = "ContainerClass")] -fn your_function_name(arg: i32) -> Result<(), String> { - // your code here +fn your_function_name(arg: String) -> Result, String> { + Ok(arg.split('/').map(|x| x.to_string()).collect()) } ``` -by specifying package and class, this crate will write an appropriate wrapper with the right function name. If inner function returns a `Result<>`, wrapper will also handle it. (currently just panics, soon will throw exceptions!) +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. + + +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. + +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!) + +to return pointer type values, add the `ptr` attribute -note that input/output arguments must be natively FFI safe: there will be no hidden translations! you will have to un-marshal strings yourself ## examples the following function: ```rust -#[jni_toolbox::jni(package = "mp.code", class = "Client")] -fn connect(env: JNIEnv, cacca: JString) -> Result<(), ConnectionError> { - let config = codemp::api::Config::new("asd".into(), "dsa".into()); - tokio().block_on(codemp::Client::connect(config))?; - Ok(()) +#[jni(package = "mp.code", class = "Client", ptr)] +fn connect(config: Config) -> Result { + tokio().block_on(Client::connect(config)) } ``` -gets turned into this couple of functions: +gets turned into these two functions: + +
show macro expansion + ```rust -fn connect(env: JNIEnv, host: JString) -> Result<(), ConnectionError> { - let config = codemp::api::Config::new("mail@example.net".into(), "dont-use-this-password".into()); - tokio::runtime::current().block_on(codemp::Client::connect(config))?; - Ok(()) +fn connect(config: Config) -> Result { + tokio().block_on(Client::connect(config)) } #[no_mangle] -pub extern "system" fn Java_mp_code_Client_connect<'local>(env: JNIEnv, host: JString) -> () { - match connect(env, cacca) { - Ok(x) => x, - Err(e) => { - $crate::panicking::panic_fmt($crate::const_format_args!("error in JNI!")); - } - } +#[allow(unused_mut)] +pub extern "system" fn Java_mp_code_Client_connect<'local>( + mut env: jni::JNIEnv<'local>, + _class: jni::objects::JClass<'local>, + mut config: >::T, +) -> >::T { + use jni_toolbox::{FromJava, IntoJava, JniToolboxError}; + let mut env_copy = unsafe { env.unsafe_clone() }; + let config_new = match jni_toolbox::from_java_static::(&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(); + } + }, + } } ``` +
+ ## 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