3

I'm trying to compile a jar file with Gradle and it works fine except when I run the jar I get an UnsatisfiedLinkError due to EvalC.dll not being found in java.library.path.

When I run the project inside IntelliJ using Gradle I just specify the following the the Gradle task and it works fine:

systemProperty "java.library.path", "$projectDir\\libs"

I added the same line to the Gradle task for building the jar but that didn't work.

I also tried adding

static
{
    System.setProperty("java.library.path", "libs");
}

to my Main class but that doesn't work either. Neither does it work using the full path. The libs directory is in the project folder along with src and resources etc. so the paths would be /ProjectFolder/libs, /ProjectFolder/src etc.

I know I can specify java.library.path on the command line (which I haven't tried because it isn't what I want). The user needs to be able to double click the jar and run it.

What is the correct way to get EvalC.dll to be found correctly without specifying command line arguments? I don't mind if it's via code or via the Gradle task.

EDIT - Minimal Example:

public class TestApp extends Application
{
    private static void loadJarDLL(String name) throws IOException
    {
        InputStream in = TestApp.class.getResourceAsStream(name);
        byte[] buffer = new byte[1024];
        int read;
        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());
    }

    public static void main(String[] args)
    {
        launch(args);
    }

    int numPlayers = 3;
    int[] playerHands = {3, 14, 2, 6, 3, 2, 8, 4, 8, 3, 7, 2, 3, 3, 2};
    int[] comCards = {11, 2, 4, 1, 10, 2, 8, 2, 2, 1};

    public static native int[] evaluateCards(int playersSize, int[] playerHands, int[] comCards);

    @Override
    public void start(Stage stage) throws IOException
    {
        try
        {
            loadJarDLL("EvalC.dll");
        }
        catch(IOException e)
        {
            e.printStackTrace();
        }

        evaluateCards(numPlayers, playerHands, comCards);
    }
}

Gradle Run:

run
{
    systemProperty "java.library.path", "$projectDir\\libs"
}

Gradle Build Jar:

jar
{
    manifest
    {
        attributes 'Main-Class': "$mainClassName"
    }
    from
    {
        configurations.compile.collect { it.isDirectory() ? it : zipTree(it) }
    }
    destinationDir = file("$rootDir/bin")
}

EDIT 2 - Link to download zipped example project:

https://drive.google.com/open?id=0B3-S5JAgvkPDTDlKOXlNYWxwdmc

James Burton
  • 746
  • 3
  • 12
  • Some libraries pack the `.dll` inside the jar, when the user tries to run the code, it unpacks the `.dll` from the jar to a temp location and loads the library from this temp location using the calculated full path to the `.dll`. [JNA library](https://github.com/java-native-access/jna) is a good example of this approach. If you have the `.dll` at some fixed location relative to your `.jar`, calculate the full path to the file and use it in the `#System.loadLibrary();` method – CrazyCoder May 10 '17 at 19:27
  • @CrazyCoder how do I find out what the temporary location is? – James Burton May 10 '17 at 19:35
  • You know it because you use a method like `#File.createTempFile()`, then unpack your `.dll` into that location. – CrazyCoder May 10 '17 at 19:37
  • @CrazyCoder I tried copying the DLL to a temp file using the code from this answer http://stackoverflow.com/questions/4691095/java-loading-dlls-by-a-relative-path-and-hide-them-inside-a-jar but it didn't work, I'm getting a NullPointerException because the InputStream in is null. I'm using my Main class to call getResourceAsStream on. – James Burton May 10 '17 at 20:01
  • There is some problem with the code then or with the jar structure, I doubt that anyone will be able to help you if you don't provide the [Minimal, Complete, and Verifiable example](http://stackoverflow.com/help/mcve). – CrazyCoder May 10 '17 at 20:02
  • @CrazyCoder I have updated my question with an example. – James Burton May 10 '17 at 20:18
  • Where is the .dll inside the jar? – CrazyCoder May 10 '17 at 20:36
  • @CrazyCoder It was in the libs directory but that doesn't get packaged into the jar, I've moved the .dll to the resources directory so now it appears in the root of the jar. Obviously this means my original method (which worked when running in IntelliJ, just not as a jar) of setting java.library.path in Gradle won't work. – James Burton May 10 '17 at 20:46
  • But this method is better since your app is easier to distribute and run on other systems. – CrazyCoder May 10 '17 at 20:48
  • @CrazyCoder Ok that's great, the issue with the input stream being null still exists though. The example I added to my question gives said exception. – James Burton May 10 '17 at 20:50
  • Share the complete project (zip and use dropbox or google drive, or onedrive, etc). – CrazyCoder May 10 '17 at 20:51
  • @CrazyCoder I have added a link to the zipped example project to the question. – James Burton May 10 '17 at 20:55
  • In your project .dll is not packaged in the root of the jar, therefore it cannot be loaded, either change `srcDirs = ['resources']` to `srcDirs = ['libs']` in your `build.gradle` file or move the .dll from the `libs` directory to the `resources` directory. – CrazyCoder May 10 '17 at 21:01
  • @CrazyCoder Just realised I changed it in the actual project but not in the test project. I've updated it now and I'm getting the following exception java.lang.UnsatisfiedLinkError: TestApp.evaluateCards(I[I[I)[I – James Burton May 10 '17 at 21:09
  • @CrazyCoder How can that be the case when it works inside IntelliJ? I've even just recompiled the DLL after double checking the signatures and everything is correct. It works if I go back to the original method of setting java.library.path, it just won't work in a jar. – James Burton May 10 '17 at 21:55
  • The problem is not with loading the library, but with finding the native method to call. The signature of the method exported from the `.dll` is `Java_Control_Poker_CardEvaluator_evaluateCards`, therefore your class must be inside `Control.Poker` package and also must have `CardEvaluator` name so that you can use `evaluateCards` native method. – CrazyCoder May 10 '17 at 21:55
  • The example you shared has `TestApp` class instead, therefore it can't call the `evaluateCards` native method form the dll, hence the error. – CrazyCoder May 10 '17 at 21:56
  • @CrazyCoder ah I understand, I was just looking at the arguments of the function not the name of the class I was calling it from. I've updated the test project, now it's back to the NullPointerException on the InputStream. – James Burton May 10 '17 at 22:01
  • You have to adjust the path of the resource, since it's relative to the class package. As the class is now not in the root package, it can't find the resource. You need to use `loadJarDLL("/EvalC.dll");` so that dll is loaded from the root of the jar where it's packaged. Here is the complete working sample project: [jar-load-native.zip](https://mega.nz/#!EtpAARJI!k93ZKf7MzCdbosg3N60nIRtFAJrw5MJHOt6ol3dAtpI). I hope you understand now how important it is to provide the valid MCVE and how it can speed up the problem resolution. – CrazyCoder May 10 '17 at 22:04
  • @CrazyCoder Thanks for your help. – James Burton May 10 '17 at 22:35
  • Welcome, don't forget to remove the temp dll file on app exit. – CrazyCoder May 10 '17 at 22:36
  • @CrazyCoder that's a good point, I'll add that in. – James Burton May 10 '17 at 22:59

0 Answers0