3

My understanding of invoking java from rust via the jni crate involves a bit of boilerplate that looks like

je.call_method(self.rimuru, "shell2Pixels", "(II[B)V", &[
    JValue::from(width),
    JValue::from(height),
    JValue::from(rgbs.as_obj()),
])?;

I am imagining that this could be improved using macros. Perhaps something like

java_call_method!(self.rimuru, "shell2Pixels", (), width, height, rgbs)?;

The macro would be responsible for building the signature string (II[B)V from the types of the various arguments.

Does something like this already exist and I have not discovered it? I am not sure if it can be implemented using regular macros instead of procedural macros.

Mutant Bob
  • 3,121
  • 2
  • 27
  • 52
  • Does the string `(II[B)V` indicate the types of the arguments you're passing, or the types the function is expecting? In the later case, a macro that would build that string would be very unsafe. – mcarton Jul 24 '20 at 20:32
  • the (II[B)V indicates the types passed to the macro. If they do not match what is available in the java class, then I expect java to throw a `NoSuchMethodException`. – Mutant Bob Jul 26 '20 at 03:23
  • This approach might work for primitive types but quickly becomes unworkable for reference types (objects and arrays). What you *could* do is interpret the given type signature at compile time and use it to type check the arguments. – Botje Jul 27 '20 at 09:20
  • support for objects and arrays would be kind of tricky, and I imagine multiple layers of support would be possible (one where you just have to pass a `jobject`, and other fancier layers where you have templatized wrapper classes). I will not be tackling that in version 1. – Mutant Bob Jul 27 '20 at 15:31

1 Answers1

1

After a great deal of wallowing in confusion I have created https://github.com/mutantbob/rust_jni_boilerplate which has a small set of procedural macros that generate boilerplate.

(as of 2020-Aug) It is used like this:

struct Widget<'a> {
    jni_env: &'a AttachGuard<'a>,
    java_this: AutoLocal<'a,'a>,
}

impl<'a> JavaConstructible<'a> for Widget<'a>
{
    fn wrap_jobject(jni_env:&'a AttachGuard<'a>, java_this: AutoLocal<'a,'a>) -> Widget<'a>
    {
        Widget {
            jni_env,
            java_this,
        }
    }
}

impl<'a> Widget<'a> {

    // define a rust function named new
    jni_constructor! { com.purplefrog.rust_callables.Widget () }
    jni_constructor! { new_one=com.purplefrog.rust_callables.Widget (&str) }

    jni_instance_method! { count () -> i32 }
    jni_instance_method! { sumLen () -> i32 }

    jni_instance_method! { add(&str) }

    jni_instance_method! { echo_str=echo(&str)->String }
    jni_instance_method! { echo_char=echo(char)->char }
    jni_instance_method! { echo_byte=echo(i8)->i8 }
    jni_instance_method! { echo_short=echo(i16)->i16 }
    jni_instance_method! { echo_int=echo(i32)->i32 }
    jni_instance_method! { echo_long=echo(i64)->i64 }

    jni_instance_method! { to_strings = toStrings() ->Vec<String> }
}

There are probably gaping holes in its type support. Its architecture has mutated drastically during development. I'm not even sure I'm using the procedural macro API idiomatically. The jni crate it is based on can hardly be considered safe (it is very easy to leak memory when you are calling java from rust). But I have managed to make it work for my limited use case.

I am hoping it serves as an inspiration for someone else to do better. Maybe it will be useful as an example of how to write procedural macros until someone writes a proper article (google search didn't exactly give me any good articles about it).

Mutant Bob
  • 3,121
  • 2
  • 27
  • 52