25

PART 1

I am developing a Java application that should be release as a jar. This program depends on C++ external libraries called by JNI. To load them, I use the method System.load with an absolute path and this works fine.

However, I really want to "hide" them inside the JAR, so I have created a package to collect them. This forces me to load an relative path - the package path. By this approach, I let the user run the JAR in any directory, without being worried about linking the DLLs or bored with a previous installation process.

This throws the expected exception:

Exception in thread "main" java.lang.UnsatisfiedLinkError: Expecting an absolute path of the library

How can I get this working?

PART 2

The approach of copying the DLLs to a folder (explained below) only works when I run it under the eclipse environment. Running an exported JAR, the DLL binaries are well created but loading the JNI one throws the next exception:

Exception in thread "main" java.lang.reflect.InvocationTargetException

 at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:56)
 Caused by: java.lang.UnsatisfiedLinkError: C:\Users\Supertreta\Desktop\nm files\temp\jniBin.dll: Can't find dependent libraries at java.lang.ClassLoader$NativeLibrary.load(Native Method)

I run this loading method:

public static void loadBinaries(){
        String os = System.getProperty("os.name").toLowerCase();

        if(os.indexOf("win") >= 0){
            ArrayList<String> bins = new ArrayList<String>(){{
                add("/nm/metadata/bin/dependence1.dll");
                add("/nm/metadata/bin/dependence2.dll");
                add("/nm/metadata/bin/dependence3.dll");
                add("/nm/metadata/bin/dependence4.dll");
                add("/nm/metadata/bin/jniBin.dll");
            }};

            File f = null;
            for(String bin : bins){
                InputStream in = FileManager.class.getResourceAsStream(bin);
                byte[] buffer = new byte[1024];
                int read = -1;
                try {
                    String[] temp = bin.split("/");
                    f = new File(TEMP_FOLDER, temp[temp.length-1]);     
                    FileOutputStream fos = new FileOutputStream(f);

                    while((read = in.read(buffer)) != -1) {
                        fos.write(buffer, 0, read);
                    }
                    fos.close();
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

            System.load(f.getAbsolutePath());
        }
    }

I think this could be an access privileges issue, but don't know how to solve it. What do you think?

Raedwald
  • 46,613
  • 43
  • 151
  • 237
supertreta
  • 359
  • 1
  • 6
  • 10

4 Answers4

26

I don't believe you can load the DLL directly from the JAR. You have to take the intermediary step of copying the DLL out of the JAR. The following code should do it:

public static void loadJarDll(String name) throws IOException {
    InputStream in = MyClass.class.getResourceAsStream(name);
    byte[] buffer = new byte[1024];
    int read = -1;
    File temp = File.createTempFile(name, "");
    FileOutputStream fos = new FileOutputStream(temp);

    while((read = in.read(buffer)) != -1) {
        fos.write(buffer, 0, read);
    }
    fos.close();
    in.close();

    System.load(temp.getAbsolutePath());
}
  • This seems me a quick and easy way to solve this issue. I tried it but have a problem: the createTempFile method appends a number to the file's name, ie the lib "hello.dll" becomes to "hello.dll4975093656535427331", even with these parameters (name, ""). My primary JNI dll also depends on others dlls so I must be aware about the names. Do you know how can I deal with this? – supertreta Jan 14 '11 at 15:20
  • 1
    Yes, just alter the way `File temp` is initialized: `File temp = new File(new File(System.getProperty("java.io.tmpdir")), name)`; –  Jan 14 '11 at 15:38
  • Now I copy all the dll files but when I load the JNI one I get the next exception: Exception in thread "main" java.lang.UnsatisfiedLinkError: C:\Documents and Settings\Administrator\Local Settings\Temp\hello.dll: This application has failed to start because the application configuration is incorrect. Reinstalling the application may fix this problem. I confirmed that all the libraries are in the temp folder... – supertreta Jan 14 '11 at 15:57
  • I was thinking and probably this is because now I am using dlls compiled in windows 7 on a XP instance. Before you have the work to answer, please let me check if this happens on 7 too. Thanks for your support. – supertreta Jan 14 '11 at 16:05
  • Hi again, I have tried this on my win7 machine and this actually works well, but only under eclipse environment. When I generate the jar and run it, the program creates the dll files under the chosen folder but throws the next exception: Exception in thread "main" java.lang.reflect.InvocationTargetException at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:56) Caused by: java.lang.UnsatisfiedLinkError: C:\Users\Supertreta\Desktop\nm files\temp\jniBin.dll: Can't find dependent libraries at java.lang.ClassLoader$NativeLibrary.load(Native Method)... – supertreta Jan 15 '11 at 14:01
  • 2
    The problem was related to the fact that I only was loading the jni dll. This is enough when running under eclipse (I don't imagine why...) but doesn't work executing a jar. In the last case, I must load all the dlls to Java environment. I also compiled two versions of these binaries, one for xp other for 7. I have now everything working properly. Thanks for you support! ps: http://stackoverflow.com/questions/1611357/how-to-make-a-jar-file-that-include-dll-files - this topic also covers these issues. – supertreta Jan 16 '11 at 15:37
2

This JarClassLoader aiming to solve the same problem:

http://www.jdotsoft.com/JarClassLoader.php

AlexV
  • 544
  • 9
  • 19
  • 1
    Note to others: although the link does not work now, it has been flagged as with License is GPLv3, which substantially limits applicability. The JarClassLoader works in the same line a s the answer from @kurt-kaylor, i.e. unpacking all libraries and loading them from outside the jar. – manuelvigarcia Mar 15 '18 at 09:31
0

Basically this should work. As this is the way JNA does it, simply download it and study the code. YOu even have some hints to make this platform independent...

EDIT

JNA brings its native code along in the jar, unpacks the correct binary at runtime und loads it. This may be a good pattern to follow (if i got your question correct).

mtraut
  • 4,720
  • 3
  • 24
  • 33
0

You need to prime the classloader with the location of the DLL -- but it can be loaded without extracting it from the jar. Something simple before the load call is executed is sufficient. In your main class add:

static {
    System.loadLibrary("resource/path/to/foo"); // no .dll or .so extension!
}

Notably, I experienced basically the same issue with JNA and OSGi's handling of how the DLLs are loaded.

Community
  • 1
  • 1
Mark Elliot
  • 75,278
  • 22
  • 140
  • 160
  • I think I run it properly. And this works just fine when I am debugging it on eclipse. The problem appears when I export a jar and run it. I updated the answer with my loading method. Thanks! – supertreta Jan 15 '11 at 18:19
  • @supertreta: I bet you're not packing the DLL into your JAR then. Try renaming your JAR to a zip file and exploring it. – Mark Elliot Jan 15 '11 at 18:20
  • I can see the jar creating the dlls on the folder. I confirmed it removing them before running it. I checked now converting it in a zip, and they are there. – supertreta Jan 15 '11 at 18:22
  • 1
    Does not work for me, I get `Directory separator should not appear in library name: resource/lib/libfoo` – malat Oct 10 '17 at 12:15