Code Monkey home page Code Monkey logo

robusta's Introduction

robusta — easy interop between Rust and Java

Build Status Latest Version Docs

Master branch docs

This library provides a procedural macro to make easier to write JNI-compatible code in Rust.

It can perform automatic conversion of Rust-y input and output types (see the limitations).

[dependencies]
robusta_jni = "0.2"

Usage

All that's needed is a couple of attributes in the right places.

First, a #[bridge] attribute on a module will enable it to be processed by robusta.

Then, we will need a struct for every class with a native method that will be implemented in Rust, and each of these structs will have to be annotated with a #[package] attribute with the name of the Java package the corresponding class belongs to.

After that, the functions implemented can be written as ordinary Rust functions, and the macro will take care of converting to and from Java types for functions marked public and with a "jni" ABI. By default if a conversion fails a Java exception is thrown.

On the other hand, if you need to call Java function from Rust, you add a "java" ABI and add a &JNIEnv parameter after self/&self/&mut self (or as first parameter if the method is static), and leave the function body empty.

On these methods you can attach a call_type attribute that manages how conversions and errors are handled: by default, #[call_type(safe)] is implied, but you can switch to #[call_type(unchecked)] at any time, most likely with few or no code changes.

You can also force a Java type on input arguments via #[input_type] attribute, which can be useful for Android JNI development for example.

Android specificities

On Android App, to call a Java class from rust the JVM use the callstack to find desired class. But when in a rust thread, you don't have a call stack anymore.
So to be able to call a Java class you have to pass the class reference rather than the string class path.

You can find an example of this usage in robusta-android-example/src/thread_func.rs

Code example

You can find an example under ./robusta-example. To run it you should have java and javac on your PATH and then execute:

$ cd robusta-example
$ make java_run

# if you don't have `make` installed:
$ cargo build && javac com/example/robusta/HelloWorld.java && RUST_BACKTRACE=full java -Djava.library.path=../target/debug com.example.robusta.HelloWorld

Usage on Android example

You can find an example of Robusta used for Android in ./robusta-android-example. To run it, open the project robustaAndroidExample with Android Studio.

Cargo build is automatically run by gradle.

The rust lib.rs is the image of the Java class RobustaAndroidExample.

This example only gets the files authorized path of the App.

Example usage

Rust side

use robusta_jni::bridge;
use robusta_jni::convert::Signature;

#[bridge]
mod jni {
    #[derive(Signature)]
    #[package(com.example.robusta)]
    struct HelloWorld;

    impl HelloWorld {
        pub extern "jni" fn special(mut input1: Vec<i32>, input2: i32) -> Vec<String> {
            input1.push(input2);
            input1.iter().map(ToString::to_string).collect()
        }
    }
}

Java side

package com.example.robusta;

import java.util.*;

class HelloWorld {
    private static native ArrayList<String> special(ArrayList<Integer> input1, int input2);

    static {
        System.loadLibrary("robusta_example");
    }

    public static void main(String[] args) {
        ArrayList<String> output = HelloWorld.special(new ArrayList<Integer>(List.of(1, 2, 3)), 4);
        System.out.println(output)
    }
}

Type conversion details and extension to custom types

There are four traits that control how Rust types are converted to/from Java types: (Try)FromJavaValue and (Try)IntoJavaValue.

These traits are used for input and output types respectively, and implementing them is necessary to allow the library to perform automatic type conversion.

These traits make use of type provided by the jni crate, however to provide maximum compatibility with robusta, we suggest using the re-exported version under robusta_jni::jni.

Raising exceptions

You can make a Rust native method raise a Java exception simply by returning a jni::errors::Result with an Err variant.

Conversion table

Rust Java
i32 int
bool boolean
char char
i8 byte
f32 float
f64 double
i64 long
i16 short
String String
Vec<T>† ArrayList<T>
Box<[u8]> byte[]
jni::JObject<'env> (any Java object as input type)
jni::jobject (any Java object as output)

† Type parameter T must implement proper conversion types

‡ The special 'env lifetime must be used

Limitations

Currently there are some limitations in the conversion mechanism:

  • Boxed types are supported only through the opaque JObject/jobject types
  • Automatic type conversion is limited to the table outlined above, though easily extendable if needed.

Contributing

I glady accept external contributions! :)

robusta's People

Contributors

ahouts avatar cedriccouton avatar dependabot[bot] avatar elisechouleur avatar giovanniberti avatar github-actions[bot] avatar he00741098 avatar uvlad7 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

robusta's Issues

Invalid Return Signature on Call Java Void function from Rust

Hello, First of all, thank you very much for this 'library'.

I am trying to call a void function from rust, but the signature from method is wrong

This is the output

> Task :Main.main() FAILED
	Call from Rust getWorld method
Error while throwing Java exception: Java exception was thrown

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

See https://docs.gradle.org/7.6.1/userguide/command_line_interface.html#sec:command_line_warnings
4 actionable tasks: 1 executed, 3 up-to-date
Exception in thread "main" java.lang.NoSuchMethodError: Linternal/Hello;.print()Ljava/lang/Object;
	at internal.Hello.world(Native Method)
	at main.Main.main(Main.java:12)

FAILURE: Build failed with an exception.
	

I think this problem is in the "Ljava/lang/Object" return.

Would it be related to this issue #8 ? Maybe is time to release a new version.

main/Main.java

package main;

import internal.Hello;

public class Main {
    public static void main(String[] args) {

        System.load("/home/palomo/libs/libcore_serve.so");

        // Java
        Hello hello = new Hello();
        System.out.println(hello.world());

    }
}

internal/Hello.java

package internal;

public class Hello {

    public native String world();

    public String getWorld() {
        System.out.println("\tCall from Rust getWorld method");
        return "World";
    }
    public void print() {
        System.out.println("\tCall from Rust print method");
    }

}

lib.rs

extern crate core;

use robusta_jni::bridge;

#[bridge]
mod jni {
    use robusta_jni::convert::{Signature, TryIntoJavaValue, TryFromJavaValue};
    use robusta_jni::jni::JNIEnv;
    use robusta_jni::jni::objects::{AutoLocal, JValue};
    use robusta_jni::jni::errors::Result as JniResult;
    use robusta_jni::jni::errors::Error as JniError;


    #[derive(Signature, TryIntoJavaValue, TryFromJavaValue)]
    #[package(internal)]
    pub struct Hello<'env: 'borrow, 'borrow> {
        #[instance]
        raw: AutoLocal<'env, 'borrow>,
    }

    impl<'env: 'borrow, 'borrow> Hello<'env, 'borrow> {
        pub extern "jni" fn world(self, env: &JNIEnv) -> String {
            self.getWorld(env);
            self.print(env);
            return "Hello World".to_string();
        }

        pub extern "java" fn getWorld(&self, env: &JNIEnv) -> JniResult<String> {}

        pub extern "java" fn print(&self, env: &JNIEnv) -> JniResult<()> {}
    }


}

[Features request] Rust JNI vs Java benchmark example

It would be nice to add an example that does some common / simple benchmarks. One uses all native Java code, and the other uses Java code that calls the same benchmarks re-coded in Rust. Just to give people an idea on whether or not this library meets their needs. Because in some cases, the performance gain may or may not be worth it.

Issue with mapping Java class fields of type `byte[]` to a `Field` with type `Box<[u8]>`

I'm trying to map a Java class having a byte[] field to a struct in rust in the way described in the README.md, but I stumble upon a weird issue with error types somewhere in the underlying trait derivations.

For the simplest possible reproduction:

  1. Fresh clone of the repo.
  2. In the rust example, change the field type here from String to Box<[u8]>.
  3. We won't even need to change the other parts of the example accordingly, since at this point as a diagnostic in VSCode exactly under the macro symbol here appears the above-mentioned compilation error:
type mismatch resolving `<*mut _jobject as TryFrom<JValueWrapper<'_>>>::Error == Error`
expected `Error`, found `Infallible`
field.rs(55, 82): required by a bound in `Field::<'env, 'borrow, T>::field_try_from`

the trait bound `*mut _jobject: From<JValueWrapper<'_>>` is not satisfied
required for `JValueWrapper<'_>` to implement `Into<*mut _jobject>`
required for `*mut _jobject` to implement `TryFrom<JValueWrapper<'_>>`

field.rs(55, 53): required by a bound in `Field::<'env, 'borrow, T>::field_try_from`
the trait bound `JObject<'_>: From<*mut _jobject>` is not satisfied
the following other types implement trait `From<T>`:
  <JObject<'a> as From<JThrowable<'a>>>
  <JObject<'a> as From<JClass<'a>>>
  <JObject<'a> as From<JString<'a>>>
  <JObject<'a> as From<JMap<'a, 'b>>>
  <JObject<'a> as From<JList<'a, 'b>>>
  <JObject<'a> as From<JByteBuffer<'a>>>
  <JObject<'a> as From<&'a GlobalRef>>
  <JObject<'a> as From<&'a AutoLocal<'a, '_>>>
required for `*mut _jobject` to implement `Into<JObject<'_>>`
required for `JValue<'_>` to implement `From<*mut _jobject>`

I tried investigating this myself, but to little avail. I suspect one of two things:

  1. A rather trivial to fix issue with resolving imports, i.e. a simple use statement of a trait implementation might fix this;
  2. I'm misunderstanding the intent behind the usage of the conversion table within fields. Perhaps this has to do with the mentioned type conversion limitations or the FIXME note here.

Funnily enough, if we try to convert a Java field of type ArrayList<byte[]> as a Vec<Box<[u8]>>, everything compiles and works. I find this strange, since the TryFromJavaValue implementation for Vec<T> generically relies on T being convertible from a java value. So for now, a feasible solution is to simply hold a list with a single value.

Any feedback on the matter would be welcome, I though this is worth mentioning as an issue. :)

TryFromJavaValue is not safe

Default implementation for TryFromJavaValue looks like the following:

                    #[automatically_derived]
                    impl<
                        'env: 'borrow,
                        'borrow,
                    > ::robusta_jni::convert::TryFromJavaValue<'env, 'borrow>
                    for RubyModule<'env, 'borrow> {
                        type Source = ::robusta_jni::jni::objects::JObject<'env>;
                        fn try_from(
                            source: Self::Source,
                            env: &'borrow ::robusta_jni::jni::JNIEnv<'env>,
                        ) -> ::robusta_jni::jni::errors::Result<Self> {
                            Ok(Self {
                                raw: ::robusta_jni::jni::objects::AutoLocal::new(
                                    env,
                                    source,
                                ),
                            })
                        }
                    }

it never fails and isn't actually safe. It allows to screw up with types and to write something like

    let xml_module: jni::RubyModule = robusta_jni::convert::TryFromJavaValue::try_from(JObject::from(env.new_string("Xml").unwrap()), &env).unwrap();

and it will compile and fail in runtime somewhere xml_module is used. If xml_module is used as a caller, java.lang.NoSuchMethodError will be - most likely - thrown, but if it's used as an argument, things become even worse, because Java doesn't check if arguments really match the signature provided, so it'll crash with some cryptic error - java.lang.NullPointerException: Cannot read the array length because "cache" is null - or even segfault.

So, maybe use is_instance_of/is_assignable_from check in TryFromJavaValue, at least in debug builds? Or mark try_from as unsafe?

(Yes, I see that things like JClass::from aren't safe and unsafe too, probably it should be changed too)

Illegal hardware instruction java - when calling Rust code from Java

I am making a test project to get familiar with the library before using it in production and I have a problem when calling a Rust function that returns custom struct to the Java code. I have a simple java client called JavaClient and the error I get when calling Rust code is illegal hardware instruction java JavaClient.

Here is the sample Java client:

// JavaClient.java
public class JavaClient {
    private static native RustResult2 stringLengthComplexGenerated(String input);

    static {
        System.loadLibrary("java_bridge");
    }

    public static void main(String[] args) {
        System.out.println("Hello from Java");

        String input = "12345678";
        String shortInput = "1234";
         // With generated models 
        RustResult2 result4 = stringLengthComplexGenerated(input);
        System.out.printf("Length of the long string (gen, models) %s is: %d, returned error is %s\n", input, result4.length, result4.error);

        RustResult2 result5 = stringLengthComplexGenerated(shortInput);
        System.out.printf("Length of the short string (gen, models) %s is: %d, returned error is %s\n", shortInput, result5.length, result5.error);
    }
}

// RustResult2.java
public class RustResult2 {
    public int length;
    public String error;

    public RustResult2(int length, String error) {
        this.length = length;
        this.error = error;
    }
} 

And here is the Rust lib:

// Cargo.toml
[package]
name = "java-bridge"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
crate-type = ["cdylib"]

[dependencies]
rust-lib = { path = "../rust-lib" }
# robusta_jni = "0.2.1"
robusta_jni = { path = "../../robusta" }
jni = "0.19.0"


// lib.rs
use robusta_jni::bridge;

#[bridge]
mod jni {
    use robusta_jni::convert::{FromJavaValue, IntoJavaValue, Signature, TryFromJavaValue, TryIntoJavaValue};
    use robusta_jni::jni::errors::Error as JniError;
    use robusta_jni::jni::errors::Result as JniResult;
    use robusta_jni::jni::objects::{AutoLocal, JValue};
    use robusta_jni::jni::sys::jobject;
    use robusta_jni::jni::JNIEnv;
    use rust_lib::{string_length_complex_internal, string_length_internal};

    #[derive(Signature, IntoJavaValue, TryIntoJavaValue, FromJavaValue, TryFromJavaValue)]
    #[package()]
    pub struct RustResult2<'env: 'borrow, 'borrow> {
        #[instance]
        raw: AutoLocal<'env, 'borrow>,
        length: i32,
        error: String,
    }

    // impl<'env: 'borrow, 'borrow> IntoJavaValue<'env> for RustResult2<'env, 'borrow> {
        // type Target = jobject;
        // fn into(self, env: &JNIEnv<'env>) -> Self::Target {

        //     let length = self.length;
        //     let error = self.error;

        //     let env: &'_ ::robusta_jni::jni::JNIEnv<'_> = env;
        //     let res = env
        //         .new_object(
        //             "RustResult2",
        //             [
        //                 "(",
        //                 <i32 as ::robusta_jni::convert::TryIntoJavaValue>::SIG_TYPE,
        //                 <String as ::robusta_jni::convert::TryIntoJavaValue>::SIG_TYPE,
        //                 ")",
        //                 "V",
        //             ]
        //                 .join(""),
        //             &[
        //                 ::std::convert::Into::into(
        //                     <i32 as ::robusta_jni::convert::TryIntoJavaValue>::try_into(
        //                         length,
        //                         &env,
        //                     ).unwrap(),
        //                 ),
        //                 ::std::convert::Into::into(
        //                     <String as ::robusta_jni::convert::TryIntoJavaValue>::try_into(
        //                         error,
        //                         &env,
        //                     ).unwrap(),
        //                 ),
        //             ],
        //         );
        //     res.unwrap().into_inner()
        // }
    // }

    impl<'env: 'borrow, 'borrow> RustResult2<'env, 'borrow> {
        #[constructor]
        pub extern "java" fn new(env: &'borrow JNIEnv<'env>, length: i32, error: String) -> JniResult<Self> {} 
    }

    #[derive(Signature)]
    #[package()]
    pub struct JavaClient;

    impl<'env: 'borrow, 'borrow> JavaClient {

        #[call_type(unchecked)]
        pub extern "jni" fn stringLengthComplexGenerated(
            env: &'borrow JNIEnv<'env>,
            input: String,
        ) -> RustResult2<'env, 'borrow> {
            let result = string_length_complex_internal(&input);
            let (length, error) = match result {
                Result::Ok(length) => {
                    (length.try_into().unwrap(), "".to_owned())
                }
                Result::Err(error) => {
                    (-1, error.to_owned())
                }
            };

            let res = RustResult2::new(env, length, error).unwrap();
            println!("Result is (length: {}, error: {})", res.length, res.error);
            res
        }
    }
}

I get the error when using IntoJavaValue in derive attribute. If I remove it and manually do the conversion (commented code) then it works. The expanded code for the IntoJavaValue is this:

#[automatically_derived]
    impl<'env: 'borrow, 'borrow> ::robusta_jni::convert::IntoJavaValue<'env>
    for RustResult2<'env, 'borrow> {
        type Target = ::robusta_jni::jni::objects::JObject<'env>;
        fn into(self, env: &::robusta_jni::jni::JNIEnv<'env>) -> Self::Target {
            ::robusta_jni::convert::IntoJavaValue::into(self, env)
        }
    }

If I copy that code instead of my manual implementation I get the same error in the runtime and recursion warning from Rust, so that might be the issue. That "illegal hardware instruction" might actually be stack overflow.

`pub` keyword in "jni" functions causing weird compiler error about unrelated code.

I am trying to set up a project using the robusta_jni crate, and today I started seeing a weird issue where the pub keyword in my "jni" (implemented on the rust side of the program) function causes a compiler error. The rust diagnostic looks like this:

error[E0599]: no method named `description` found for enum `robusta_jni::jni::errors::Error` in the current scope
   --> src\lib.rs:13:9
    |
13  |         pub extern "jni" fn printFromRust() -> JniResult<()> {
    |         ^^^ method not found in `Error`
    |
   ::: C:\Users\MY_NAME\.rustup\toolchains\stable-x86_64-pc-windows-msvc\lib/rustlib/src/rust\library\core\src\error.rs:110:8
    |
110 |     fn description(&self) -> &str {
    |        ----------- the method is available for `robusta_jni::jni::errors::Error` here
    |
    = help: items from traits can only be used if the trait is in scope
help: the following trait is implemented but not in scope; perhaps add a `use` for it:
    |
5   +     use std::error::Error;
    |

Meanwhile, the code that's causing the error is very simple (because I'm just trying to make sure everything works at this point). The file it's in doesn't even use robusta_jni::jni::errors::Error.

...
pub extern "jni" fn printFromRust() -> JniResult<()> {
    println!("Rust called from Java.");
    Ok(())
}

Adding the use statement like the diagnostic suggests turns the error into a warning about the description method being deprecated, and this may be what I have to do for now to get the code to build.

I also tried copying and pasting the example code from this GitHub repo into the file and all of the "jni" functions in that code had the exact same issue, which is why I think it may be a bug in the robusta_jni crate. The last time the code didn't have this issue was before Rust 1.70 was released, so maybe that update broke something,

Java inheritance

And support - if not supported yet - and tests.
The best way, I believe, is to make Rust wrapper of a child java class a NewType for a Rust wrapper of the according Java ancestor, with Deref trait implemented. It won't work exactly like in Java - with virtual methods - but it'll allow to call ancestor's methods from the descendant.

Error::description() deprecated, should use Display trait

Hi there! I just started using your library, and it worked perfectly well the first time I used it (2 days ago). The IDE was complaining about description() being deprecated, but it was only a warning and everything was working perfectly.

However now I'm getting a RuntimeException in Java whenever I try to call my JNI function, with the following message:

java.lang.RuntimeException: JNI call error!. Cause: description() is deprecated; use Display

This is quite confusing, because why on earth is this compile-time check inside the generated shared library file itself?! I couldn't really figure out what's causing this.

My only hypothesis for now is that the Error::description() function is automatically populated with that message to get the attention of developers to use Display instead, which makes it useless to understand what's going on under-the-hood.

Windows support

Is windows supported? Cause I'm not able to compile it on Windows

UPD: Problem is not related to robusta

Exit code 11 (segfault?) when converting a java class to a JObject

So I have this java class binded through rust:

    #[derive(Signature, TryIntoJavaValue, IntoJavaValue, TryFromJavaValue)]
    #[package(com.theoparis.elementalbridge.core)]
    pub struct Item<'env: 'borrow, 'borrow> {
        #[instance]
        raw: AutoLocal<'env, 'borrow>,
        #[field]
        pub id: Field<'env, 'borrow, String>,
        #[field]
        pub name: Field<'env, 'borrow, String>,
    }

    impl<'env: 'borrow, 'borrow> Item<'env, 'borrow> {
        #[constructor]
        pub extern "java" fn new(
            env: &'borrow JNIEnv<'env>,
            id: String,
            name: String,
        ) -> JniResult<Self> {
        }
    }

And I have a rust function that needs to convert the Item struct to a JObject so it can be used in another struct method (that I'm using to "register the item").

pub fn register_item<'a>(ctx: &ModContext<'a>, mod_info: ModInfo, item: &Item) {
    let registry =
        Registry::new(ctx.jni, mod_info.id, "item".to_string()).unwrap();

    let java_item =
        crate::jni::Item::new(ctx.jni, item.id.clone(), item.name.clone())
            .unwrap();

    debug!("Registering item: {}", item.name);

    let java_obj =
        robusta_jni::convert::IntoJavaValue::into(java_item, ctx.jni);
    debug!("Java object: {:?}", java_obj);
    registry.register(ctx.jni, java_obj).unwrap();
    info!("Registered item: {}", item.name);
}

It does print the registering item message, but then after a second or two the whole java process crashes with exit code 11 - which I'm assuming is a segmentation fault since im on linux.

Any ideas as to why this could be happening? There isn't a discussions page so I assumed creating an issue would be the best place to get help...

Refactor `Field` type to have class path and field name as const parameters

This would change Field definition from

struct Field<'env: 'borrow, 'borrow, T> { /* ... */ }

to

struct Field<'env: 'borrow, 'borrow, const CLASSPATH_PATH: &str, const FIELD_NAME: &str, T> { /* ... */ }

This change would allow:

  1. More type safety around fields (you could not directly assign a class field where another class field is expected), even though I don't know how much useful this kind of safety would be
  2. Removal of the #[field] attribute in *FromJavaValue derive macros for Field fields

Moreover, in *FromJavaValue derive macros we could still ensure that the two constant parameters match the declared package and class name.

With some macro trickery we could perhaps include a "pseudo type macro" Field! that expands to the correct Field<...> type and does not actually exist but is instead manually expanded by the derive macros.

Blocked by rust-lang/rust#44580

Support for Option<T>?

Hiiii <3

Will this project support passing Option to JNI? There are many APIs in the Android Sdk that might involve nullable values, soo this would help a lot!!!

Apparent error in string conversion

I have a struct with

impl<'env,'borrow> MyStruct<'env,'borrow> {
  pub extern "java" fn message(&self,_env:&'borrow JNIEnv<'env>,_s:String) -> JniResult<bool> {}
}

...but when calling this function, I get an Error:

Error(JavaException, State { next:error:None, bracktrace: InternalBacktrace { backtrace: Some(
  0: error_chain::backtrace::imp::InternalBacktrace::new
  1: <error_chain::State as core::default::Default>::default
  2: <T as core::convert::Into<U>>::into
  3: jni::wrapper::jnienv::JNIEnv::new_string
  4: <alloc::string::String as robusta_jni::convert::safe::TryIntoJavaValue>::try_into
  5: ...::message
  ...
}}

...which looks to me like a bug in the string conversion to java. It's difficult to find the precise cause, since I can't set break points in rust when running the program from java (or, actually, Scala, but that shouldn't make a difference).

Any help would be very much appreciated :)

Throw an exception specific to the returned error instance

As far as I understand, the exception class and message are constants for a given fallible method, irrespective of the error that might be returned:

#[call_type(safe(exception_class = "com.example.MyException", message = "Something bad happened"))]
pub extern "jni" fn bar(foo: i32) -> ::robusta_jni::jni::errors::Result<i32> { Ok(foo) }

This is unhelpful to Java callers as it doesn't indicate the reason behind the failure, but only that it failed in a specific native method (information already indicated by the stack trace anyway). The thrown exception has no relationship to the actual error returned.

Instead of the current behavior, I suggest throwing an exception that is based on the error instance itself. If we can assume that the error type implements jni::errors::ToException, then calling jni::errors::ToException::to_exception() on the returned error instance returns a jni::errors::Exception holding enough information to throw an exception object of a specific class and message.

For instance:

use jni::errors::{Exception, ToException};

impl ToException for MyError {
    fn to_exception(&self) -> Exception {
        Exception {
            class: "com/example/MyException".into(),
            msg: self.to_string(),
        }
    }
}

Support for primitive arrays in combination with Signature

Hey. I was wondering what the current approach is to mix structs annotated with #[derive(Signature)] where the impl needs to have a method that accepts e.g. jni's jlongarray. I thought I could fall back to using jni directly by passing &JNIEnv but that does not seem to work if the remaining arguments are jobject.

Is there a way to disable the signature resolution for certain functions of a struct? Thank you.

What I tried so far is:

pub extern "jni" fn dotProductArray<'env>(
    env: &'env JNIEnv,
    vector_a: LongArray<'env>,
    vector_b: LongArray<'env>,
) -> i64 {
    // Wrap the pointer to the java array into an AutoArray, which automatically
    // releases the pointer once the variable goes out of scope.
    super::dot_product(&vector_a.to_vec(env), &vector_b.to_vec(env))
}

and

#[repr(C)]
pub struct LongArray<'env>(JObject<'env>);

impl<'env> Signature for LongArray<'env> {
    const SIG_TYPE: &'static str = "[I)J";
}

impl<'env> JavaValue<'env> for LongArray<'env> {
    fn autobox(
        self,
        _env: &robusta_jni::jni::JNIEnv<'env>,
    ) -> robusta_jni::jni::objects::JObject<'env> {
        todo!()
    }

    fn unbox(
        s: robusta_jni::jni::objects::JObject<'env>,
        _env: &robusta_jni::jni::JNIEnv<'env>,
    ) -> Self {
        Self(s)
    }
}

impl<'env> LongArray<'env> {
    fn to_vec(&'env self, env: &'env JNIEnv) -> Vec<i64> {
        let len = env.get_array_length(self.0.into_inner()).unwrap();
        println!("len = {len}");
        let mut vec = vec![0; len as usize];
        let _ = env.get_long_array_region(self.0.into_inner(), 0, &mut vec);
        println!("vec = {vec:?}");
        vec
    }
}

This works, however, I actually would like to avoid allocating a Vec. Do you have a suggestion?

Maybe it would make sense to re-export https://docs.rs/jni/latest/jni/struct.JNIEnv.html#method.get_long_array_elements as well?

Also, would it be an option to separate the unbox and autobox into different traits, and only allow the unbox in the argument position and autobox for return types?

[Features request] Mark the type of Error in rust

I want to got the types of errors in java. but JniError::ThrowFailed becomes RuntimeException in Java. Use of RuntimeException is not conducive to error analysis. I hope to get code and message accurately. Or you can customize java Throwable.

"missing `#[package]` attribute" when deriving TryFromJavaValue but not Signature

I attempted a fix at https://github.com/kitlith/robusta/tree/fix_missing_package, which fixes the ostensible issue, but then realized that this "solution" is going to cause issues when you're not deriving Signature, because the classpath specified in Signature will not match the classpath generated for the derive macros.

I think the real/better solution here is to put the classpath in the signature trait as well as the signature itself, or some complication thereof. In my case, I cannot rely on the ident of the struct because i'm trying to work with an inner class Outer$Inner, and rust identifiers cannot contain '$'

inb4 there's another way you want to do this that I didn't think of.

Overloaded methods support

  • For "jni" methods - use long method name format. This will also make it more error-proof, protecting against inconsistent types in java and rust declarations, now it's possible to write something like
        pub extern "jni" fn getBool(self, v: bool) -> bool {
            v
        }
    public native Boolean getBool(Boolean x);

and it won't fail on System.loadLibrary call

  • For "java" methods - allow to override corresponding java method name to be able to write something like
        #[java_name(getPassword)]
        pub extern "java" fn getPasswordString(
            &self,
            env: &JNIEnv,
        ) -> JniResult<String> {}

        #[java_name(getPassword)]
        pub extern "java" fn getPasswordBytes(
            &self,
            env: &JNIEnv,
        ) -> JniResult<Box<[i8]>> {}
    public String getPassword() {
        return password;
    }
    public byte[] getPassword() {
        return password.getBytes();
    }

Release

Hello. I depend on functionality of returning JniResult it the java->rust calls and because of that I have to depend on the git branch. Could you please make a release so I can depend on a version instead?

Why is SIG_TYPE for `()` set to "Ljava/lang/Object;"?

 impl Signature for () {
     const SIG_TYPE: &'static str = "Ljava/lang/Object;";
 }

if i don't understand wrong, () is equivalent to void in java. so should it be

 impl Signature for () {
    const SIG_TYPE: &'static str = "V";
}

? i tested some java functions like void org.apache.hadoop.fs.FSDataInputStream#seek(Path path); robusta-jni-0.2 reports error about function not found. after changing signature type for () to "V" it works.

document pre-requisite setup for different distros

Fedora Rawhide

Here are the pre-requisites to get the robust-example built and running successfully on Fedora Rawhide:

dnf install java-latest-openjdk-demo java-latest-openjdk-devel
export JAVA_HOME=/usr/lib/jvm/java-16-openjdk-16.0.0.0.36-2.rolling.fc35.x86_64
export PATH=$JAVA_HOME/bin:$PATH

If all goes well, you'll have a path like this:

echo $PATH
/usr/lib/jvm/java-16-openjdk-16.0.0.0.36-2.rolling.fc35.x86_64/bin:/home/davidm/.cargo/bin:/home/davidm/.local/bin:/home/davidm/bin:/usr/local/bin:/usr/local/sbin:/usr/bin:/usr/sbin

Now fetch the robusta sources and build the robusta-example:

git clone https://github.com/giovanniberti/robusta.git
cd robusta/robusta-example
make java_run
cargo build
    Finished dev [unoptimized + debuginfo] target(s) in 0.03s
javac com/example/robusta/HelloWorld.java && RUST_BACKTRACE=full java -Djava.library.path=../target/debug com.example.robusta.HelloWorld
[1, 2, 3, 4]
3
3

Enjoy

No way to return an error with arbitrary message

I have the "catch all panics" wrapper around the code and return a JNI error in this case. With the introduction of errors enum in 0.2.1 this stopped working. Right now JniError::Unknown looks like the best type for this case but there is no way to also provide the error message.

Change result type for exceptions from `::robusta_jni::jni::errors::Result<T>` to `Result<T, E>`

In recent jni versions jni::errors::Result its error variant can no longer be constructed from a string. making the type useless for library code and/or application usage.

We should refactor this by allowing any result type to be capable of be used as a return type in #[call_type(safe)] methods.
Also, in doing so we could use as the default exception message the return value of E::description() (where E the type of the error variant)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.