Java for Rust

Overview

j4rs

crates.io Maven Central Build Status Build status

j4rs stands for 'Java for Rust' and allows effortless calls to Java code from Rust and vice-versa.

Features

Usage

Basics

use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};

// Create a JVM
let jvm = JvmBuilder::new().build()?;

// Create a java.lang.String instance
let string_instance = jvm.create_instance(
    "java.lang.String",     // The Java class to create an instance for
    &Vec::new(),            // The `InvocationArg`s to use for the constructor call - empty for this example
)?;

// The instances returned from invocations and instantiations can be viewed as pointers to Java Objects.
// They can be used for further Java calls.
// For example, the following invokes the `isEmpty` method of the created java.lang.String instance
let boolean_instance = jvm.invoke(
    &string_instance,       // The String instance created above
    "isEmpty",              // The method of the String instance to invoke
    &Vec::new(),            // The `InvocationArg`s to use for the invocation - empty for this example
)?;

// If we need to transform an `Instance` to Rust value, the `to_rust` should be called
let rust_boolean: bool = jvm.to_rust(boolean_instance)?;
println!("The isEmpty() method of the java.lang.String instance returned {}", rust_boolean);
// The above prints:
// The isEmpty() method of the java.lang.String instance returned true

// Static invocation
let _static_invocation_result = jvm.invoke_static(
    "java.lang.System",     // The Java class to invoke
    "currentTimeMillis",    // The static method of the Java class to invoke
    &Vec::new(),            // The `InvocationArg`s to use for the invocation - empty for this example
)?;

// Access a field of a class
let system_class = jvm.static_class("java.lang.System")?;
let system_out_field = jvm.field(&system_class, "out");

// Retrieve an enum constant using the field
let access_mode_enum = jvm.static_class("java.nio.file.AccessMode")?;
let access_mode_write = jvm.field(&access_mode_enum, "WRITE")?;

Passing arguments from Rust to Java

j4rs uses the InvocationArg enum to pass arguments to the Java world.

Users can benefit of the existing TryFrom implementations for several basic types:

let i1 = InvocationArg::try_from("a str")?;      // Creates an arg of java.lang.String
let my_string = "a string".to_owned();
let i2 = InvocationArg::try_from(my_string)?;    // Creates an arg of java.lang.String
let i3 = InvocationArg::try_from(true)?;         // Creates an arg of java.lang.Boolean
let i4 = InvocationArg::try_from(1_i8)?;         // Creates an arg of java.lang.Byte
let i5 = InvocationArg::try_from('c')?;          // Creates an arg of java.lang.Character
let i6 = InvocationArg::try_from(1_i16)?;        // Creates an arg of java.lang.Short
let i7 = InvocationArg::try_from(1_i64)?;        // Creates an arg of java.lang.Long
let i8 = InvocationArg::try_from(0.1_f32)?;      // Creates an arg of java.lang.Float
let i9 = InvocationArg::try_from(0.1_f64)?;      // Creates an arg of java.lang.Double

And for Vecs:

let my_vec: Vec<String> = vec![
    "abc".to_owned(),
    "def".to_owned(),
    "ghi".to_owned()];

let i10 = InvocationArg::try_from(my_vec.as_slice())?;

The Instances returned by j4rs can be transformed to InvocationArgs and be further used for invoking methods as well:

let one_more_string_instance = jvm.create_instance(
    "java.lang.String",     // The Java class to create an instance for
    &Vec::new(),            // The `InvocationArg`s to use for the constructor call - empty for this example
)?;

let i11 = InvocationArg::try_from(one_more_string_instance)?;

To create an InvocationArg that represents a null Java value, use the From implementation with the Null struct:

let null_string = InvocationArg::from(Null::String);                // A null String
let null_integer = InvocationArg::from(Null::Integer);              // A null Integer
let null_obj = InvocationArg::from(Null::Of("java.util.List"));    // A null object of any other class. E.g. List

Casting

An Instance may be casted to some other Class:

let instantiation_args = vec![InvocationArg::try_from("Hi")?];
let instance = jvm.create_instance("java.lang.String", instantiation_args.as_ref())?;
jvm.cast(&instance, "java.lang.Object")?;

Java arrays and variadics

// Create a Java array of Strings
let s1 = InvocationArg::try_from("string1")?;
let s2 = InvocationArg::try_from("string2")?;
let s3 = InvocationArg::try_from("string3")?;

let arr_instance = jvm.create_java_array("java.lang.String", &vec![s1, s2, s3])?;
// Invoke the Arrays.asList(...) and retrieve a java.util.List<String>
let list_instance = jvm.invoke_static("java.util.Arrays", "asList", &[InvocationArg::from(arr_instance)])?;

Java Generics

// Assuming that the following map_instance is a Map<String, Integer>
// we may invoke its put method
jvm.invoke(&map_instance, "put", &vec![InvocationArg::try_from("one")?, InvocationArg::try_from(1)?])?;

Java primitives

Even if auto boxing and unboxing is in place, j4rs cannot invoke methods with primitive int arguments using Integer instances.

For example, the following code does not work:

let ia = InvocationArg::try_from(1_i32)?;
jvm.create_instance("java.lang.Integer", &[ia])?;

It throws an InstantiationException because the constructor of Integer takes a primitive int as an argument:

Exception in thread "main" org.astonbitecode.j4rs.errors.InstantiationException: Cannot create instance of java.lang.Integer at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:37) Caused by: java.lang.NoSuchMethodException: java.lang.Integer.(java.lang.Integer) at java.base/java.lang.Class.getConstructor0(Class.java:3349) at java.base/java.lang.Class.getConstructor(Class.java:2151) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.createInstance(NativeInstantiationImpl.java:69) at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:34)

In situations like this, the java.lang.Integer instance should be transformed to a primitive int first:

let ia = InvocationArg::try_from(1_i32)?.into_primitive()?;
jvm.create_instance("java.lang.Integer", &[ia]);

Java instances chaining

use j4rs::{Instance, InvocationArg, Jvm, JvmBuilder};

// Create a JVM
let jvm = JvmBuilder::new().build()?;

// Create an instance
let string_instance = jvm.create_instance(
    "java.lang.String",
    &vec![InvocationArg::try_from(" a string ")?],
)?;

// Perform chained operations on the instance
let string_size: isize = jvm.chain(string_instance)
    .invoke("trim", &[])?
    .invoke("length", &[])?
    .to_rust()?;

// Assert that the string was trimmed
assert!(string_size == 8);

Callback support

j4rs provides support for Java to Rust callbacks.

These callbacks come to the Rust world via Rust Channels.

In order to initialize a channel that will provide Java callback values, the Jvm::invoke_to_channel should be called. It returns a result of InstanceReceiver struct, which contains a Channel Receiver:

// Invoke of a method of a Java instance and get the returned value in a Rust Channel.

// Create an Instance of a class that supports Native Callbacks
// (the class just needs to extend the 
// `org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport`)
let i = jvm.create_instance(
    "org.astonbitecode.j4rs.tests.MyTest",
    &Vec::new())?;

// Invoke the method
let instance_receiver_res = jvm.invoke_to_channel(
    &i,                         // The instance to invoke asynchronously
    "performCallback",          // The method to invoke asynchronoysly
    &Vec::new()                 // The `InvocationArg`s to use for the invocation - empty for this example
);

// Wait for the response to come
let instance_receiver = instance_receiver_res?;
let _ = instance_receiver.rx().recv();

In the Java world, a Class that can do Native Callbacks must extend the org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport

For example, consider the following Java class.

The performCallback method spawns a new Thread and invokes the doCallback method in this Thread. The doCallback method is inherited by the NativeCallbackToRustChannelSupport class.

package org.astonbitecode.j4rs.tests;

import org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport;

public class MyTest extends NativeCallbackToRustChannelSupport {

    public void performCallback() {
        new Thread(() -> {
            doCallback("THIS IS FROM CALLBACK!");
        }).start();
    }

}

Using Maven artifacts

Since release 0.6.0 there is the possibility to download Java artifacts from the Maven repositories. While it is possible to define more repos, the maven central is by default and always available.

For example, here is how the dropbox dependency can be downloaded and get deployed to be used by the rust code:

let dbx_artifact = MavenArtifact::from("com.dropbox.core:dropbox-core-sdk:3.0.11");
jvm.deploy_artifact(dbx_artifact)?;

Additional artifactories can be used as well:

let jvm: Jvm = JvmBuilder::new()
    .with_maven_settings(MavenSettings::new(vec![
        MavenArtifactRepo::from("myrepo1::https://my.repo.io/artifacts"),
        MavenArtifactRepo::from("myrepo2::https://my.other.repo.io/artifacts")])
    )
    .build()
    ?;

jvm.deploy_artifact(&MavenArtifact::from("io.my:library:1.2.3"))?;

Maven artifacts are added automatically to the classpath and do not need to be explicitly added.

A good practice is that the deployment of maven artifacts is done by build scripts, during the crate's compilation. This ensures that the classpath is properly populated during the actual Rust code execution.

Note: the deployment does not take care the transitive dependencies yet.

Adding jars to the classpath

If we have one jar that needs to be accessed using j4rs, we need to add it in the classpath during the JVM creation:

let entry = ClasspathEntry::new("/home/myuser/dev/myjar-1.0.0.jar");
let jvm: Jvm = JvmBuilder::new()
    .classpath_entry(entry)
    .build()?;

j4rs Java library

The jar for j4rs is available in the Maven Central. It may be used by adding the following dependency in a pom:

<dependency>
    <groupId>io.github.astonbitecode</groupId>
    <artifactId>j4rs</artifactId>
    <version>0.13.0</version>
    <scope>provided</scope>
</dependency>

Note that the scope is provided. This is because the j4rs Java resources are always available with the j4rs crate.

Use like this in order to avoid possible classloading errors.

j4rs in android

Rust side

  1. Define your crate as cdylib in the Cargo.toml:
[lib]
name = "myandroidapp"
crate-type = ["cdylib"]
  1. Implement a jni_onload function and apply the provided JavaVM to the j4rs like following:
const JNI_VERSION_1_6: jint = 0x00010006;

#[allow(non_snake_case)]
#[no_mangle]
pub extern fn jni_onload(env: *mut JavaVM, _reserved: jobject) -> jint {
    j4rs::set_java_vm(env);
    jni_version_1_6
}

Java side

Create an Activity and define your native methods normally, as described here.

Note: If you encounter any issues when using j4rs in Android, this may be caused by Java 8 compatibility problems. This is why there is a Java 7 version of j4rs:

<dependency>
    <groupId>io.github.astonbitecode</groupId>
    <artifactId>j4rs</artifactId>
    <version>0.13.1-java7</version>
</dependency>

JavaFX support

(v0.13.0 onwards)

Steps to build a JavaFX UI

1. Have Rust, cargo and JDK 11 (or above) installed

2. Retrieve the JavaFX dependencies for j4rs:

A good idea is that this happens during build time, in order the dependencies to be available when the actual Rust application starts and the JVM is initialized. This can happen by adding the following in a build script:

	use j4rs::JvmBuilder;
	use j4rs::jfx::JavaFxSupport;

	fn main() {
		let jvm = JvmBuilder::new().build().unwrap();
		jvm.deploy_javafx_dependencies().unwrap();
	}

3. Implement the UI:

There are two choices here; either build the UI using FXML, or, build it traditionally, using Java code. In the code snippets below, you may find comments with a short description for each line.

3.a Implement the UI with Java calls to the JavaFX API
// Create a Jvm with JavaFX support
let jvm = JvmBuilder::new().with_javafx_support().build()?;

// Start the JavaFX application.
// When the JavaFX application starts, the `InstanceReceiver` channel that is returned from the `start_javafx_app` invocation
// will receive an Instance of `javafx.stage.Stage`.
// The UI may start being built using the provided `Stage`.
let stage = jvm.start_javafx_app()?.rx().recv()?;

// Create a StackPane. Java code: StackPane root = new StackPane();
let root = jvm.create_instance("javafx.scene.layout.StackPane", &[])?;

// Create the button. Java code: Button btn = new Button();
let btn = jvm.create_instance("javafx.scene.control.Button", &[])?;
// Get the action channel for this button
let btn_action_channel = jvm.get_javafx_event_receiver(&btn, FxEventType::ActionEvent_Action)?;
// Set the text of the button. Java code: btn.setText("Say Hello World to Rust");
jvm.invoke(&btn, "setText", &["A button that sends events to Rust".try_into()?])?;
// Add the button to the GUI. Java code: root.getChildren().add(btn);
jvm.chain(&root)?
	.invoke("getChildren", &[])?
	.invoke("add", &[btn.try_into()?])?
	.collect();

// Create a new Scene. Java code: Scene scene = new Scene(root, 300, 250);
let scene = jvm.create_instance("javafx.scene.Scene", &[
	root.try_into()?,
	InvocationArg::try_from(300_f64)?.into_primitive()?,
	InvocationArg::try_from(250_f64)?.into_primitive()?])?;
// Set the title for the scene. Java code: stage.setTitle("Hello Rust world!");
jvm.invoke(&stage, "setTitle", &["Hello Rust world!".try_into()?])?;
// Set the scene in the stage. Java code: stage.setScene(scene);
jvm.invoke(&stage, "setScene", &[scene.try_into()?])?;
// Show the stage. Java code: stage.show();
jvm.invoke(&stage, "show", &[])?;
3.b Implement the UI with FXML

I personally prefer building the UI with FXMLs, using for example the Scene Builder.

The thing to keep in mind is that the controller class should be defined in the root FXML element and it should be fx:controller="org.astonbitecode.j4rs.api.jfx.controllers.FxController"

Here is an FXML example; it creates a window with a label and a button:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.Button?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.Font?>

<VBox alignment="TOP_CENTER" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="725.0" spacing="33.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="org.astonbitecode.j4rs.api.jfx.controllers.FxController">
   <children>
      <Label text="JavaFX in Rust">
         <font>
            <Font size="65.0" />
         </font>
      </Label>
      <Label text="This UI is loaded with a FXML file" />
      <HBox alignment="CENTER" prefHeight="100.0" prefWidth="200.0" spacing="10.0">
         <children>
            <Button id="helloButton" mnemonicParsing="false" text="Say Hello" />
         </children>
      </HBox>
   </children>
</VBox>

The id of the elements can be used to retrieve the respective Nodes in Rust and act upon them (eg. adding Event Listeners, changing the texts or effects on them etc).

// Create a Jvm with JavaFX support
let jvm = JvmBuilder::new().with_javafx_support().build()?;

// Start the JavaFX application.
// When the JavaFX application starts, the `InstanceReceiver` channel that is returned from the `start_javafx_app` invocation
// will receive an Instance of `javafx.stage.Stage`.
// The UI may start being built using the provided `Stage`.
let stage = jvm.start_javafx_app()?.rx().recv()?;

// Set the title for the scene. Java code: stage.setTitle("Hello Rust world!");
jvm.invoke(&stage, "setTitle", &["Hello JavaFX from Rust!".try_into()?])?;
// Show the stage. Java code: stage.show();
jvm.invoke(&stage, "show", &[])?;

// Load a fxml. This returns an `FxController` which can be used in order to find Nodes by their id,
// add Event Listeners and more.
let controller = jvm.load_fxml(&PathBuf::from("./fxml/jfx_in_rust.fxml"), &stage)?;

// Wait for the controller to be initialized. This is not mandatory, it is here to shoe that the functionality exists.
let _ = controller.on_initialized_callback(&jvm)?.rx().recv()?;
println!("The controller is initialized!");

// Get the InstanceReceiver to retrieve callbacks from the JavaFX button with id helloButton
let hello_button_action_channel = controller.get_event_receiver_for_node("helloButton", FxEventType::ActionEvent_Action, &jvm)?;

For a complete example, please have a look here.

Java to Rust support

(v0.12.0 onwards)

  • Add the two needed dependencies (j4rs and j4rs_derive) in the Cargo.toml and mark the project as a cdylib, in order to have a shared library as output. This library will be loaded and used by the Java code to achieve JNI calls.

  • Annotate the functions that will be accessible from the Java code with the call_from_java attribute:

#[call_from_java("io.github.astonbitecode.j4rs.example.RustSimpleFunctionCall.fnnoargs")]
fn my_function_with_no_args() {
    println!("Hello from the Rust world!");
    // If you need to have a Jvm here, you need to attach the thread
    let jvm = Jvm::attach_thread().unwrap();
    // Now you may further call Java classes and methods as usual!
}

For a complete example, please have a look here.

Note: JNI is used behind the scenes, so, any conventions in naming that hold for JNI, should hold for j4rs too. For example, underscores (_) should be escaped and become _1 in the call_from_java definition.

Portability assumptions after Rust build (shipping a j4rs application)

During build, j4rs creates a jassets directory which contains the "java world" that is needed for the crate to work. It is always automatically populated with Java libraries and can be considered something like a default classpath container that should always be available.

By default, jassets lies in the same directory with the crate-generated artifacts (under CARGO_TARGET_DIR), so there should not be any issues during development.

But how can the application be shipped after the implementation is done?

Someone may specify a different base_path for j4rs during the Jvm initialization, issuing something like:

let jvm_res = j4rs::JvmBuilder::new()
        .with_base_path("/opt/myapp")
        .build();

The base_path defines the location of two directories that are needed for j4rs to work; namely jassets and deps.

  1. jassets contains the j4rs jar and other jars that may be deployed using Maven.
  2. deps should contain the j4rs dynamic library. This is needed to achieve callbacks from java to rust. The deps dir is not needed if the application does not execute Java->Rust callbacks.

So, someone may have their application binary under eg. /usr/bin, and the jassets and deps directories under /opt/myapp/, or $HOME/.myapp, or anywhere else.

An example directory tree could be:

/ 
+ --- usr
|      + --- bin
|             + --- myapp
| 
+ --- opt
       + --- myapp 
              + --- jassets
              + --- deps

Moreover, there is also a utility function that automatically performs copying of the two directories under a specific path. The Jvm::copy_j4rs_libs_under function can be called by the build script of the crate that is being shipped:

Jvm::copy_j4rs_libs_under("/opt/myapp")?;

After that, /opt/myapp will contain everything that is needed in order j4rs to work, as long as the Jvm creation is done using the with_base_path method:

let jvm_res = j4rs::JvmBuilder::new()
        .with_base_path("/opt/myapp")
        .build();

Licence

At your option, under:

Comments
  • Unable to cross-compile to macos from linux

    Unable to cross-compile to macos from linux

    I've made a Docker image (Linux) to cross-compile my Rust project to macOS targets (x86_64-apple-darwin). For that I'm using osxcross as the cross-compiler. However, I'm getting the following error when building my project:

       Compiling j4rs v0.11.0
    error: failed to run custom build command for `j4rs v0.11.0`
    
    Caused by:
      process didn't exit successfully: `/project/target/release/build/j4rs-9e299e02ed1913c3/build-script-build` (exit code: 1)
    --- stderr
    Error: J4rsBuildError { description: "JavaLocatorError { description: \"Command \\\'/usr/libexec/java_home\\\' is not found in the system PATH (entity not found)\" }" }
    

    I took a quick look into the build script, and it seems like it will conditionally look for the dynamic jvm library if target_os is macOS, however, this fails since the actual host is not a macOS architecture.

        } else if target_os == "macos" {
            let ld_library_path = java_locator::locate_jvm_dyn_library()?;
            let ld = env::var("DYLD_LIBRARY_PATH").unwrap_or("".to_string());
            println!("cargo:rustc-env=DYLD_LIBRARY_PATH={}:{}", ld_library_path, ld);
            println!("cargo:rustc-link-search={}", ld_library_path);
        }
    

    I've also tried to comment out this if clause, however, then it tries to link to the jvm library during linking.

       Compiling j4rs v0.11.0
    error: linking with `x86_64-apple-darwin14-clang` failed: exit code: 1
      |
      = note: ... linker flags omitted ...
      = note: ld: library not found for -ljvm
              clang: error: linker command failed with exit code 1 (use -v to see invocation)
    
    
    error: aborting due to previous error
    
    error: could not compile `j4rs`.
    
    To learn more, run the command again with --verbose.
    

    I understand if this is not tested nor the scope of this project, but I wonder if you have any idea how I could solve this?

    BTW, cross-compiling j4rs for linux and windows using Docker works nicely.

    opened by tiepettersen 17
  • Passing Integer from Java to Rust panics

    Passing Integer from Java to Rust panics

    Hi! I'm having an issue with the library (which is great btw). I'm unable to pass Integer from Java to Rust, as it panics. I'm running this on Android btw, in an emulator on a Macbook Pro m1, with Java 11.

    This are some simplified versions of the code I'm using:

    public class Rust {
        private static native Instance fnSign(Instance<String> message, Instance<String> key, Instance<Integer> parties, Instance<String> address);
    
        public Signature sign(String message, String key, String address) {
            Integer parties = Integer.valueOf(55);
            Instance instance = fnSign(
                    Java2RustUtils.createInstance(message),
                    Java2RustUtils.createInstance(key),
                    Java2RustUtils.createInstance(parties),
                    Java2RustUtils.createInstance(address));
            return Java2RustUtils.getObjectCasted(instance);
        }
    }
    
    #[call_from_java("expo.modules.multipartyecdsa.Rust.fnSign")]
    fn sign(
        message: Instance,
        key: Instance,
        parties: Instance,
        address: Instance,
    ) -> Result<Instance> {
        let jvm = Jvm::attach_thread()?;
    
        let message: String = jvm.to_rust(message)?;
        let key: String = jvm.to_rust(key)?;
        let parties: i32 = jvm.to_rust(parties).unwrap();
        let address: String = jvm.to_rust(address)?;
    
        let signature = Signature {
            r: format!("message: {}, key: {}, address: {}", message, key, address),
            s: format!("parties: {:?}", parties),
            v: 1,
        };
    
        let ia = InvocationArg::new(&signature, "expo.modules.multipartyecdsa.Signature");
        let instance = Instance::try_from(ia)?;
        Ok(instance)
    }
    

    Something interesting that happens is that if I pass it as an array int[] it works.

    If you have any further thing I can try or if you need any other information I'm all ears. Thanks!

    opened by aon 11
  • `Jvm::attach_thread` fails on Android

    `Jvm::attach_thread` fails on Android

    Jvm::attach_thread fails on Android.

    Looking at the code (https://github.com/astonbitecode/j4rs/blob/1e177113024332ebaae9aca837c01888fa009bfd/rust/src/api_tweaks/android.rs#L43), it seems like it doesn't actually call JNI_GetCreatedJavaVMs. Am I missing something?

    opened by s1341 9
  • How to enable sanitizer?

    How to enable sanitizer?

    I'm writing a library with j4rs and it works great! However when I try to enable sanitizer following this instruction it shows the following error:

    java.lang.UnsatisfiedLinkError: /home/ubuntu/j4rs-sanitizer-example/target/x86_64-unknown-linux-gnu/debug/deps/libj4rs_sanitizer_example.so: /home/ubuntu/j4rs-sanitizer-example/target/x86_64-unknown-linux-gnu/debug/deps/libj4rs_sanitizer_example.so: undefined symbol: __asan_option_detect_stack_use_after_return
    	at java.base/java.lang.ClassLoader$NativeLibrary.load0(Native Method)
    	at java.base/java.lang.ClassLoader$NativeLibrary.load(ClassLoader.java:2445)
    	at java.base/java.lang.ClassLoader$NativeLibrary.loadLibrary(ClassLoader.java:2501)
    	at java.base/java.lang.ClassLoader.loadLibrary0(ClassLoader.java:2700)
    	at java.base/java.lang.ClassLoader.loadLibrary(ClassLoader.java:2662)
    	at java.base/java.lang.Runtime.loadLibrary0(Runtime.java:830)
    	at java.base/java.lang.System.loadLibrary(System.java:1873)
    	at org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport.initialize(NativeCallbackToRustChannelSupport.java:33)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    	at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    	at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.invokeMethod(JsonInvocationImpl.java:179)
    	at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.invokeStatic(JsonInvocationImpl.java:80)
    Tracer caught signal 11: addr=0x100000680 pc=0x55d076488f8a sp=0x7f843728ed40
    ==28056==LeakSanitizer has encountered a fatal error.
    ==28056==HINT: For debugging, try setting environment variable LSAN_OPTIONS=verbosity=1:log_threads=1
    ==28056==HINT: LeakSanitizer does not work under ptrace (strace, gdb, etc
    

    I'm running the code on a ubuntu instance, and here is a simple example code to reproduce: https://github.com/wangxiaoying/j4rs-sanitizer-example

    Error occurs as long as jvm has been initiated.

    opened by wangxiaoying 8
  • Can not build: JavaLocatorError OS X

    Can not build: JavaLocatorError OS X

    I am having trouble building with j4rs and get the following error:

    Error: J4rsBuildError { description: "JavaLocatorError { description: \"Could not find the libjvm.* library in any subdirectory of /System/Library/Frameworks/JavaVM.framework/Versions/Current\" }" }

    System Details:

    • OS X Mojave 10.14.5
    • JDK 1.8.0_152
    • cargo 1.35.0 (6f3e9c367 2019-04-04)
    • j4rs 0.6.0

    Contents of above directory appears to be a symbolic link

    > ls -l /System/Library/Frameworks/JavaVM.framework/Versions/Current
    lrwxr-xr-x  1 root  wheel  1 Mar  5 10:26 /System/Library/Frameworks/JavaVM.framework/Versions/Current -> A
    

    Contents of sym link:

    > ls -l /System/Library/Frameworks/JavaVM.framework/Versions/A
    total 80
    drwxr-xr-x  52 root  wheel    1664 Jun  4 05:37 Commands
    drwxr-xr-x   4 root  wheel     128 Sep 20  2018 Frameworks
    drwxr-xr-x   3 root  wheel      96 Aug 17  2018 JavaPluginCocoa.bundle
    -rwxr-xr-x   1 root  wheel  107984 May  3 20:53 JavaVM
    drwxr-xr-x  50 root  wheel    1600 Jun  4 05:37 Resources
    drwxr-xr-x   3 root  wheel      96 Jun  4 05:37 _CodeSignature
    

    This error is new and only started appearing since I've updated OS X and the JDK through homebrew.

    Please let me know if I can provide any other details or perform any additional steps to help debug this issue.

    I appreciate your time.

    opened by anthonyjchriste 7
  • Having trouble building this crate

    Having trouble building this crate

    Hi, I'd like to use this crate, but I'm having trouble building it. I'm running macOS High Sierra 10.13.4, I've set the JAVA_HOME path variable, but when I try to use j4rs in my crate, I get this error at compile time.

    error: failed to run custom build command for `j4rs v0.1.2`
    process didn't exit successfully: `/Users/kahlo/Desktop/rust_tower_workspace/target/debug/build/j4rs-c083517ddcb7c3ed/build-script-build` (exit code: 101)
    --- stdout
    cargo:rustc-env=LD_LIBRARY_PATH=/Library/Java/Home/lib/server
    cargo:rustc-link-search=native=/Library/Java/Home/lib/server
    
    --- stderr
    thread 'main' panicked at 'Initialize Environment: J4rsBuildError { description: "NotPresent" }', libcore/result.rs:945:5
    stack backtrace:
       0: std::sys::unix::backtrace::tracing::imp::unwind_backtrace
                 at libstd/sys/unix/backtrace/tracing/gcc_s.rs:49
       1: std::sys_common::backtrace::print
                 at libstd/sys_common/backtrace.rs:71
                 at libstd/sys_common/backtrace.rs:59
       2: std::panicking::default_hook::{{closure}}
                 at libstd/panicking.rs:211
       3: std::panicking::default_hook
                 at libstd/panicking.rs:227
       4: <std::panicking::begin_panic::PanicPayload<A> as core::panic::BoxMeUp>::get
                 at libstd/panicking.rs:511
       5: std::panicking::continue_panic_fmt
                 at libstd/panicking.rs:426
       6: std::panicking::try::do_call
                 at libstd/panicking.rs:337
       7: <T as core::any::Any>::get_type_id
                 at libcore/panicking.rs:92
       8: core::result::unwrap_failed
                 at /Users/travis/build/rust-lang/rust/src/libcore/macros.rs:26
       9: <core::result::Result<T, E>>::expect
                 at /Users/travis/build/rust-lang/rust/src/libcore/result.rs:809
      10: build_script_build::main
                 at ./build.rs:24
      11: std::rt::lang_start::{{closure}}
                 at /Users/travis/build/rust-lang/rust/src/libstd/rt.rs:74
      12: std::panicking::try::do_call
                 at libstd/rt.rs:59
                 at libstd/panicking.rs:310
      13: panic_unwind::dwarf::eh::read_encoded_pointer
                 at libpanic_unwind/lib.rs:105
      14: std::sys_common::at_exit_imp::push
                 at libstd/panicking.rs:289
                 at libstd/panic.rs:392
                 at libstd/rt.rs:58
      15: std::rt::lang_start
                 at /Users/travis/build/rust-lang/rust/src/libstd/rt.rs:74
      16: <build_script_build::J4rsBuildError as core::fmt::Debug>::fmt
    

    Any advice?

    opened by gretchenfrage 7
  • Trouble calling a variadic constructor

    Trouble calling a variadic constructor

    Are variadic methods supported? If not, what could be a temporary workaround?

    I am trying to call this constructor from JavaParser using the following:

    const COMBINED_TYPE_SOLVER: &str =
        "com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver";
    const REFLECTION_TYPE_SOLVER: &str =
        "com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver";
    
    fn main() -> Result<(), Box<dyn Error>> {
        let jvm = JvmBuilder::new().classpath_entries(classpaths()).build()?;
        let reflection_ts = jvm.create_instance(REFLECTION_TYPE_SOLVER, &[])?;
        let combined_ts =
            jvm.create_instance(COMBINED_TYPE_SOLVER, &[InvocationArg::from(reflection_ts)])?;
        Ok(())
    }
    

    and I get the following exception:

    Exception in thread "main" org.astonbitecode.j4rs.errors.InstantiationException: Cannot create instance of com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver
    	at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:36)
    Caused by: java.lang.NoSuchMethodException: com.github.javaparser.symbolsolver.resolution.typesolvers.CombinedTypeSolver.<init>(com.github.javaparser.symbolsolver.resolution.typesolvers.ReflectionTypeSolver)
    	at java.base/java.lang.Class.getConstructor0(Class.java:3349)
    	at java.base/java.lang.Class.getConstructor(Class.java:2151)
    	at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.createInstance(NativeInstantiationImpl.java:59)
    	at org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl.instantiate(NativeInstantiationImpl.java:33)
    Error: JavaError("An Exception was thrown by Java while creating global ref... Please check the logs or the console.")
    
    opened by fredmorcos 6
  • Method 'foobar' was not found when calling instance from Rust

    Method 'foobar' was not found when calling instance from Rust

    Hey. I'm playing around with your crate and I want to try some examples for calling Rust code from Java.

    I'm stuck at using a callback to let rust call a method on a Java object.

    Basically I have

    public class Main {
        @FunctionalInterface
        public static interface Callback {
            void call(long value);
        }
        
        private static native void dotproductcallback(Instance<long[]> vectorA, Instance<long[]> vectorB, Instance<Callback> callback);
        
        static {
            System.loadLibrary("mylib");
        }
        
        public static void main(String[] args) {
            var callback = new Callback() {
                public void call(long result) {
                    System.out.println("dot product = " + result);
                }
            };
            Main.dotproductcallback(
                Java2RustUtils.createInstance(vectorA),
                Java2RustUtils.createInstance(vectorB),
                Java2RustUtils.createInstance(callback)
            );
        }
    }
    

    and

    #[call_from_java("j4rs.Main.dotproductcallback")]
    fn dot_product_callback(vector_a: Instance, vector_b: Instance, callback: Instance) {
        // Attach current thread to an active JVM
        let jvm = Jvm::attach_thread().unwrap();
        // Get the vectors from the instances
        // Note, that those are owned vecs, changing them has no effect on the original data.
        let vector_a: Vec<i64> = jvm.to_rust(vector_a).unwrap();
        let vector_b: Vec<i64> = jvm.to_rust(vector_b).unwrap();
    
        // Compute the dot product
        let dot_product = dot_product(&vector_a, &vector_b);
        let ia = InvocationArg::try_from(dot_product).unwrap();
    
        // Invoke callback method
        let _ = jvm.invoke(&callback, "call", &[ia]).unwrap();
    }
    

    When I execute this it doesn't seem to be able to find the call method:

    Method call was not found in j4rs.Main$1 or its ancestors.
    

    I tried creating a class Callback and also tried the channel example. Both lead to the same result of the method call not being found on the instance. I also tried using Callback as a class and instantiating it in Rust and not passing it down. Same result, couldn't find the method. Any suggestions?

    opened by s1ck 5
  • Fails while trying to get a value from vector

    Fails while trying to get a value from vector

    Hi,

    I am trying to get a value from a vector object but it says the method not found. The method returns a vector object that needs to be iterated. Is there a way we can do it?

    let ia = vec![InvocationArg::try_from(0).unwrap()]; let sub_folders = jvm.chain(&root_folder).unwrap() .invoke("getSubFolders", &[]).unwrap() .invoke("get", &[]).unwrap() .collect();

    Exception in thread "main" org.astonbitecode.j4rs.errors.InvocationException: While invoking method get of Class java.util.Vector at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.invoke(JsonInvocationImpl.java:73) Caused by: java.lang.NoSuchMethodException: Method get was not found in java.util.Vector or its ancestors. at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.findMethodInHierarchy(JsonInvocationImpl.java:237) at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.findMethodInHierarchy(JsonInvocationImpl.java:239) at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.findMethodInHierarchy(JsonInvocationImpl.java:239) at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.findMethodInHierarchy(JsonInvocationImpl.java:239) at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.invokeMethod(JsonInvocationImpl.java:169) at org.astonbitecode.j4rs.api.invocation.JsonInvocationImpl.invoke(JsonInvocationImpl.java:70) thread 'main' panicked at 'calledResult::unwrap()on anErrvalue: JavaError("An Exception was thrown by Java... Please check the logs or the console.")', src\main.rs:29:29 stack backtrace:

    Is there a way we can iterate over an instance ?

    opened by naveenann 5
  • `No Java runtime present, requesting install.` on OSX

    `No Java runtime present, requesting install.` on OSX

    Using j4rs version 0.13, on OSX 10.15.6, j4rs show the output No Java runtime present, requesting install. when using OracleJDK 14.02.

    JAVA_HOME is set to point to the location of the JDK.

    Here's the output from a DEBUG level log:

    running 1 test
    20:33:38 [INFO] Setting classpath to -Djava.class.path=/Users/schmidmt/code/target/debug/jassets/j4rs-0.13.0-jar-with-dependencies.jar:../../simulator/Sim.jar
    20:33:38 [INFO] Setting library path to -Djava.library.path=/Users/schmidmt/code/target/debug/deps:/usr/lib:/lib
    20:33:38 [INFO] Passing to the Java world the name of the library to load: j4rs
    20:33:38 [DEBUG] j4rs::logger: Creating a Jvm
    20:33:38 [INFO] No JVMs exist. Creating a new one...
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_get_method_id
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_get_static_method_id
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_new_object
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_new_string_utf
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_get_string_utf_chars
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_release_string_utf_chars
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_call_object_method
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_call_float_method
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_call_double_method
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_call_void_method
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_call_static_object_method
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_new_object_array
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_set_object_array_element
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_exception_check
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_exception_describe
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_exception_clear
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_delete_local_ref
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_delete_global_ref
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_new_global_ref
    20:33:39 [DEBUG] j4rs::logger: Called set_jni_throw_new
    20:33:39 [DEBUG] j4rs::logger: Called set_is_same_object
    20:33:39 [DEBUG] j4rs::logger: Called set_thread_local_env
    20:33:39 [DEBUG] j4rs::logger: Initializing NativeCallbackSupport with libname j4rs
    20:33:39 [DEBUG] j4rs::logger: Invoking static method initialize of class org.astonbitecode.j4rs.api.invocation.NativeCallbackToRustChannelSupport using 1 arguments
    20:33:39 [DEBUG] j4rs::logger: Called set_factory_class
    20:33:39 [DEBUG] j4rs::logger: Called set_factory_create_for_static_method
    20:33:39 [DEBUG] j4rs::logger: Called set_invocation_arg_class
    20:33:39 [DEBUG] j4rs::logger: Creating jobject from Rust basic for class java.lang.String
    20:33:39 [DEBUG] j4rs::logger: Calling the InvocationArg constructor with 'java.lang.String'
    20:33:39 [DEBUG] j4rs::logger: Called set_inv_arg_basic_rust_constructor_method
    20:33:39 [DEBUG] j4rs::logger: Called set_java_instance_class
    20:33:39 [DEBUG] j4rs::logger: Called set_invoke_static_method
    20:33:39 [DEBUG] j4rs::logger: Dropping an instance of known_in_java_world
    20:33:39 [DEBUG] j4rs::logger: Dropping an instance of java.lang.String
    20:33:39 [DEBUG] j4rs::logger: NativeCallbackSupport initialized
    20:33:39 [DEBUG] j4rs::logger: Instantiating class ab.demo.KDLSimulation using 0 arguments
    20:33:39 [DEBUG] j4rs::logger: Called set_factory_instantiate_method
    20:33:39 [DEBUG] j4rs::logger: Invoking method solve of class ab.demo.KDLSimulation using 1 arguments
    20:33:39 [DEBUG] j4rs::logger: Creating jobject from Rust basic for class java.lang.String
    20:33:39 [DEBUG] j4rs::logger: Calling the InvocationArg constructor with 'java.lang.String'
    20:33:39 [DEBUG] j4rs::logger: Called set_invoke_method
    ./logs/sim49243.log%g.txt 500000000 40 false
    No Java runtime present, requesting install.
    error: test failed, to rerun pass '--bin scibird-agent'
    

    This code runs correctly on debian 10.5.

    Any ideas?

    opened by schmidmt 5
  • java.lang.NoClassDefFoundError on NativeInstantiationImpl without using OracleJdk

    java.lang.NoClassDefFoundError on NativeInstantiationImpl without using OracleJdk

    I'm using j4rs successfully in some testing code.

    However, since I will need to use a different version of vm than oracle, I uninstalled OracleJdk version 8 and 9 (which I had on my windows 10 64 bit) and installed an openJdk 14.0.2.

    At this point, after solving a path problem related to jvm.dll, I got this output by trying again to perform a test related to a static method call java.lang.System.currentTimeMillis (j4rs doc example)

    /.cargo/bin/cargo.exe run --color=always --package java2rust --bin java2rust Finished dev [unoptimized + debuginfo] target(s) in 0.21s Running target\debug\java2rust.exe test_call_static_method: 308 microseconds WARNING in native method: JNI call made with exception pending WARNING in native method: JNI call made with exception pending Exception in thread "Thread-1" java.lang.NoClassDefFoundError: org/astonbitecode/j4rs/api/instantiation/NativeInstantiationImpl Caused by: java.lang.ClassNotFoundException: org.astonbitecode.j4rs.api.instantiation.NativeInstantiationImpl at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:602) at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522) thread 'main' panicked at 'called Result::unwrap() on an Err value: JavaError("An Exception was thrown by Java while creating global ref... Please check the logs or the console.")',

    I don't think it's an OpenJdk configuration problem as the same test done using the jni-rs cargo works (but if you need more information about it I'm here).

    Looking for a bit of how to solve the problem I ended up in this page that I think (but I don't know if) it can help: https://developer.android.com/training/articles/perf-jni.html#exceptions

    opened by CatCode79 4
  • How to pass an `Instance` as a parameter into a Java function and retain ownership?

    How to pass an `Instance` as a parameter into a Java function and retain ownership?

    I would like to pass a particular Instance as a parameter into a Java function and then use it later, but InvocationArg::try_from will take ownership of it. What should I do? like this:

    let entry = ClasspathEntry::new("./mirai-core-all.jar");
    jvm = JvmBuilder::new().classpath_entry(entry).build().unwrap();
    instance1 = jvm.create_instance("class_name", &[]).unwrap();
    instance2 = jvm.create_instance("class_name", &[]).unwrap();
    invoke(
         &instance1,
         "method_name",
         &[InvocationArg::try_from(instance2).unwrap()],
     );
     let a = instance2; //then compiler error about instance2 moved
    
    opened by Leart-Loen 0
Owner
aston
aston
Guarding 是一个用于 Java、JavaScript、Rust、Golang 等语言的架构守护工具。借助于易于理解的 DSL,来编写守护规则。Guarding is a guardians for code, architecture, layered.

Guarding Guarding is a guardians for code, architecture, layered. Using git hooks and DSL for design guard rules. Usage install cargo install guarding

Inherd OS Team (硬核开源小组) 47 Dec 5, 2022
eJNI is a Rust crate to make working with Java's JNI less painful by providing abstractions.

eJNI provides abstractions for often-used classes from the Java standard library, like Map and List. Besides this eJNI also provides easy ways to work with Java's primitives and their object counterparts (e.g int and Integer).

Tobias de Bruijn 11 Feb 13, 2022
Java for Rust

j4rs j4rs stands for 'Java for Rust' and allows effortless calls to Java code from Rust and vice-versa. Features Rust to Java direction support (call

aston 355 Dec 28, 2022
Rust bindings to the Java Native Interface — JNI

JNI Bindings for Rust This project provides complete JNI bindings for Rust, allowing to: Implement native Java methods for JVM and Android in Rust Cal

null 768 Dec 30, 2022
Slitter is a C- and Rust-callable slab allocator implemented primarily in Rust, with some C for performance or to avoid unstable Rust features.

Slitter is a less footgunny slab allocator Slitter is a classically structured thread-caching slab allocator that's meant to help write reliable long-

Backtrace Labs 133 Dec 5, 2022
A Rust crate for automatically generating C header files from Rust source file.

Please be aware that this crate is no longer actively maintained, please look into the much more feature rich cbindgen instead. rusty-cheddar rusty-ch

Sean Marshallsay 190 Nov 12, 2022
Rust-ffi-guide - A guide for doing FFI using Rust

Using unsafe for Fun and Profit A guide to traversing the FFI boundary between Rust and other languages. A rendered version is available here. This gu

Michael Bryan 261 Dec 1, 2022
Rust library for build scripts to compile C/C++ code into a Rust library

A library to compile C/C++/assembly into a Rust library/application.

Alex Crichton 1.3k Dec 21, 2022
Rust based WASM/JS bindings for ur-rust

ur-wasm-js WASM/JS bindings for the ur-rust rust library Getting started Installation Either build the library yourself with wasm-pack or install for

Lightning Digital Entertainment 5 Feb 28, 2024
A project for generating C bindings from Rust code

cbindgen   Read the full user docs here! cbindgen creates C/C++11 headers for Rust libraries which expose a public C API. While you could do this by h

Ryan Hunt 1.7k Jan 3, 2023
Automatically generates Rust FFI bindings to C (and some C++) libraries.

bindgen bindgen automatically generates Rust FFI bindings to C (and some C++) libraries. For example, given the C header doggo.h: typedef struct Doggo

The Rust Programming Language 3.2k Jan 4, 2023
Safe interop between Rust and C++

CXX — safe FFI between Rust and C++ This library provides a safe mechanism for calling C++ code from Rust and Rust code from C++, not subject to the m

David Tolnay 4.4k Jan 7, 2023
Safe Rust bridge for creating Erlang NIF functions

Rustler Documentation | Getting Started | Example Rustler is a library for writing Erlang NIFs in safe Rust code. That means there should be no ways t

Rusterlium 3.5k Jan 7, 2023
Bridge the gap between Haskell and Rust

Curryrs Curryrs (a play on the name of Haskell Curry, rs for Rust libraries, and it's pronunciation couriers) is a library for providing easy to use b

Michael Gattozzi 296 Oct 18, 2022
Rust in Haskell FFI Example

Provides an example for using Rust in Haskell. To use this you'll need cargo, rustc, cabal and GHC installed. To execute the example run the following

Michael Gattozzi 21 Oct 1, 2022
Rust-JDBC bindings

jdbc A Rust library that allows you to use JDBC and JDBC drivers. Usage First, add the following to your Cargo.toml: [dependencies] jdbc = "0.1" Next,

Aurora 18 Feb 9, 2022
Lua 5.3 bindings for Rust

rust-lua53 Aims to be complete Rust bindings for Lua 5.3 and beyond. Currently, master is tracking Lua 5.3.3. Requires a Unix-like environment. On Win

J.C. Moyer 150 Dec 14, 2022
Safe Rust bindings to Lua 5.1

rust-lua Copyright 2014 Lily Ballard Description This is a set of Rust bindings to Lua 5.1. The goal is to provide a (relatively) safe interface to Lu

Lily Ballard 124 Jan 5, 2023
Zero-cost high-level lua 5.3 wrapper for Rust

td_rlua This library is a high-level binding for Lua 5.3. You don't have access to the Lua stack, all you can do is read/write variables (including ca

null 47 May 4, 2022