2

I need to integrate this lib written in java in my rust crate.

So I'm trying to make a hello_world to call java function from rust. I'm using JNI bindings for rust. I'm based on docs and this question. But this is not working...

Once my java class is written I'm compiling with javac from hello folder and then generating c headers.

To compile

# from hello folder
javac HelloWorld.java

To generate headers

# from root project directory
javac main/java/org/hello/HelloWorld.java -h .

This is my project structure

|main/java/org/hello/
|-HelloWorld.java
|-HelloWorld.class
|src/
|-lib.rs
|-main.rs
|target/
|Cargo.toml
|org_hello_HelloWorld.h

this is my HelloWorld.java

package org.hello;

public class HelloWorld {
    private static native String hello(String input);

    static {
        System.loadLibrary("java-ffi");
    }
    
    public static void main(String[] args) {
        System.out.println("Hello world from java");
    }
}

this is my lib.rs where i write the binding

use jni::JNIEnv;
use jni::objects::{JClass, JString};

#[no_mangle]
#[allow(non_snake_case)]
pub extern "system" fn Java_org_hello_HelloWorld_hello(env: JNIEnv, class: JClass, _s: JString) {

    let result = env.call_method(class, "main", "()V", &[]).unwrap();

    println!("{:#?}", result);
}

this is my main.rs where i try to call the hello world

mod lib;

use jni::objects::JString;
use jni::{InitArgsBuilder, JNIVersion, JavaVM}; 

fn main() {
    let jvm_args = InitArgsBuilder::new()
        .version(JNIVersion::V8)
        .option("-Xcheck:jni")
        .build()
        .unwrap();

    let jvm = JavaVM::new(jvm_args).unwrap();
    let _guard = jvm.attach_current_thread().unwrap();

    let env = jvm.get_env().unwrap();

    let class = env
        .find_class("org/hello/HelloWorld")
        .expect("Error on class");

    let s = env.new_string("").unwrap();

    lib::Java_org_hello_HelloWorld_hello(env, class, s);
}

this is the error when i make cargo run

thread 'main' panicked at 'Error on class: JavaException', src/main.rs:20:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Exception in thread "Thread-0" java.lang.NoClassDefFoundError: org/hello/HelloWorld
Caused by: java.lang.ClassNotFoundException: org.hello.HelloWorld
        at java.net.URLClassLoader.findClass(URLClassLoader.java:387)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
        at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:351)

also based on this example from official repo i try to load the lib with java -Djava.library.path=mylib/target/debug/ HelloWorld

# from root project dir
java -Djava.library.path=target/debug main.java.org.hello.HelloWorld

but this gives me this error

Error: Could not find or load main class main.java.org.hello.HelloWorld

So, I don't understand what I'm missing. Can anyone explain me what is wrong with my code? And what is the correct form to make this?

I understand the problem is with the jvm which is not reading my java class, but I don't understand why...

additionally this is my Cargo.toml

[package]
name = "java-ffi"
version = "0.1.0"
edition = "2021"

[lib]
crate_type = ["cdylib"]

[dependencies]

[dependencies.jni]
version = "0.19"
features = [
    "invocation",
    "default"
]

Update:

I'm changing the package name of the java class, to match with folder path, from

package org.hello;

to

package main.java.org.hello;

So, the the error change. When i make cargo run

thread 'main' panicked at 'Error on class: JavaException', src/main.rs:20:10
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
Exception in thread "Thread-0" java.lang.UnsatisfiedLinkError: no java-ffi in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
        at java.lang.Runtime.loadLibrary0(Runtime.java:871)
        at java.lang.System.loadLibrary(System.java:1124)
        at main.java.org.hello.HelloWorld.<clinit>(HelloWorld.java:7)

which is the same error when now i try load the library with

java -Djava.library.path=target/debug/ main.java.org.hello.HelloWorld
Exception in thread "main" java.lang.UnsatisfiedLinkError: no java-ffi in java.library.path
        at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
        at java.lang.Runtime.loadLibrary0(Runtime.java:871)
        at java.lang.System.loadLibrary(System.java:1124)
        at main.java.org.hello.HelloWorld.<clinit>(HelloWorld.java:7)

which means the class are now 'recognized' but are bad linked, but i don't know why...

3
  • Are you using maven or gradle to set up your Java project? Commented Sep 13, 2022 at 8:12
  • The repo I want integrate uses maven, but I don't really know so much java I don't understand how configure a new project over the project I want wrap. Currently I can executes methods from rust with simple classes the problem was the lib was not loading dynamically, I solved passing the full path of the .so lib. But now I'm trying to understand how to config the java project Commented Sep 13, 2022 at 8:51
  • I just pointed out an answer that addresses the issue that you opened. If you like to know how to configure some Java tool to automate your project, you should open a new question targeting that topic. Sure you'll get a response also. Commented Sep 13, 2022 at 10:16

2 Answers 2

1

Your issue is the path where the Java classloader it's trying to find your dylib.

You may desire to solve it by specifiying the full path to the dylib in your configuration properties, so the classloader it's able to load it propertly into your project.

Sign up to request clarification or add additional context in comments.

Comments

0

After a few days of experimenting, the problem is indeed the way the dynamic library is loaded.

From here 2 things to keep in mind 1 the host OS and 2 the --release flag of cargo compilation.

Based on the operating system, rust's jni module will generate a .so, .dll, or .dylib library, depending on the case linux, windows or mac respectively.

The dynamic library output generated by the rust jni module will have the same name as the crate in snake_case. And depending on the build type debug or with the --release flag for production this output will be in $root_project_dir/target/debug, or $root_project_dir/target/release.

Based on this would be an example to load the library in java depending on the operating system without taking into account the type of compilation.

static {
        /*
        * Note: there are probably better 
        * and more elegant ways to write this 
        * but java is not my strong point and 
        * for the purpose it works
        */

        String os = System.getProperty("os.name", "generic").toLowerCase(Locale.ENGLISH);

        if (os.equals("mac")) {
            Path currentRelativePath = Paths.get("");
            Path p = currentRelativePath.toAbsolutePath();
            String fname = "target/debug/libnest4rs.dylib";
            Path fp = p.resolve(fname);
            System.load(fp.toString());
        } else if (os.equals("win")) {
            Path currentRelativePath = Paths.get("");
            Path p = currentRelativePath.toAbsolutePath();
            String fname = "target\\debug\\libnest4rs.dll";
            Path fp = p.resolve(fname);
            System.load(fp.toString());
        } else {
            Path currentRelativePath = Paths.get("");
            Path p = currentRelativePath.toAbsolutePath();
            String fname = "target/debug/libnest4rs.so";
            Path fp = p.resolve(fname);
            System.load(fp.toString());
        }
   }

As an additional tip

In my case I had problems compiling the dependencies of the libraries I want to bind to, and generating it's c headers. Something that is proving useful to me was compiling the dependencies and the library per se in .jar files. And also add my current working directory to the java classpath.

As additional info

To correctly call the java code from rust, the name of the packages must be consistent with the routes based on the root of the rust project.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.