1

I'm currently trying to call some C++ code from Java using JNI. To understand how this would work, I followed a tutorial. I have had some hiccups, but now I am almost there. So far, I have successfully created the Java Class, implemented a method in C++, compiled the code from my windows cmd, and created the library. The last thing I'm struggling with and cannot seem to figure out is how to correctly refer to the library when running the code from the command line.

My folder structure is as follows

.
├── src
    └── main
        └── java
             └── com
                 └── baeldung
                     └── jni
                          └── com_baeldung_jni_HelloWorldJNI.cpp
                          └── com_baeldung_jni_HelloWorldJNI.cpp~
                          └── com_baeldung_jni_HelloWorldJNI.h
                          └── com_baeldung_jni_HelloWorldJNI.o
                          └── HelloWorldJNI.class
                          └── HelloWorldJNI.java
                          └── native.dll

The general set-up for running the code is:

java -cp . -Djava.library.path=/NATIVE_SHARED_LIB_FOLDER com.baeldung.jni.HelloWorldJNI

First of all, it took me some time to figure out the folder from which I had to call this in cmd, to not get the error

Error: could not find or load main class com.baeldung.jni.HelloWorldJNI

I solved this issue calling the previous line while being in the src/main/java directory. I have tried different options for the library path, but all returned

Exception in thread "main" java.lang.UnsatisfiedLinkError: no native in java.library.path
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1860)
    at java.lang.Runtime.loadLibrary0(Runtime.java:870)
    at java.lang.System.loadLibrary(System.java:1122)
    at com.baeldung.jni.HelloWorldJNI.<clinit>(HelloWorldJNI.java:6)

Any suggestions on how to set the correct relative library path are more than welcome!

Thanks a lot in advance!

Student NL
  • 409
  • 6
  • 16
  • Does this answer your question? [How to call C++ from Java?](https://stackoverflow.com/questions/7593334/how-to-call-c-from-java) – pringi Feb 28 '22 at 09:57
  • @pringi unfortunately not. I did figure out just now how to get it working with an absolute path, but I would prefer to use a relative path, however, I am not sure from which folder it should start. – Student NL Feb 28 '22 at 10:00
  • @user16320675 I tried before, but with "/com/baeldung/jni' and that didn't work. Removing "/" before "com" did resolve the issue :D. Thank you! – Student NL Feb 28 '22 at 11:05
  • You might want to try to use a tool like JavaCPP that abstracts all this away so we don't need to worry about all that: https://github.com/bytedeco/javacpp – Samuel Audet Mar 04 '22 at 00:35

1 Answers1

0

Not a direct answer, just some notes what works for me in a similar situation.

My project creates a jar that consists of some java code and a native dll as resource. The following steps are performed so it works in all my use cases:

  • I have a class that handles resources. The lookup via the classLoader ensures that it also works when packaged in a fat jar.
public class Resources {
    private static final Logger LOGGER = Logger.getLogger(Resources.class.getName());

    public static <T> InputStream getResourceAsStream(Class<T> clazz, String name) {
        final var classLoader = clazz.getClassLoader();
        if (classLoader != null) {
            final var result = classLoader.getResourceAsStream(name);
            if (result != null) {
                LOGGER.info("using resource '" + name + "' obtained via the classLoader '" + classLoader.getName() + "'.");
                return result;
            }
        }
        LOGGER.info("using resource '" + name + "' obtained via class '" + clazz.getName() + "'.");
        return clazz.getResourceAsStream(name);
    }

    public static <T> void extractResourceToFilesystem(Class<T> clazz, final Path destination, final String resourceFilename) {
        if (!Files.isDirectory(destination)) {
            throw new IllegalArgumentException("The configuration path '" + destination.toAbsolutePath() + "' must be a directory");
        }
        if (!Files.isWritable(destination)) {
            throw new IllegalArgumentException("The java process must have write permissions in directory '" + destination.toAbsolutePath() + "'");
        }

        final Path resourcePath = destination.resolve(resourceFilename);
        try(final InputStream in = Resources.getResourceAsStream(clazz, "/" + resourceFilename)) {
            Files.copy(in, resourcePath, StandardCopyOption.REPLACE_EXISTING);
        } catch (IOException ex) {
            throw new IllegalArgumentException(ex.getMessage());
        }

        if (!Files.exists(resourcePath)) {
            throw new IllegalArgumentException("The file '" + resourcePath.toAbsolutePath() + "' could not be created.");
        }
        LOGGER.info("Successfully extracted resource to " + resourcePath.toAbsolutePath());
    }
}

  • The class that uses the native library first writes the dll to the application directory. This directory is always searched from loadLibrary. I use a singleton that is loaded lazily (you could do the initialization in a static block but I need this for reasons that are not important here)
  class MyClass {
  
    public native void foo(...);
    public native int bar(...);
    
    private MyClass() {
      Resources.extractResourceToFilesystem(MyClass.class, Paths.get(""), "myDll.dll");
      System.loadLibrary("myDll");
    }
    
    private static class LazyHolder {
        private final static MyClass INSTANCE = new MyClass();
    }

    public static MyClass getInstance() {
        return LazyHolder.INSTANCE;
    }

  }

MadScientist
  • 3,390
  • 15
  • 19