Exporting Java game using Jogl into ONLY one Jar in Eclipse

Ok, what I tried was moving the DLL files into a folder “test” I placed in the root directory. Then I put these two lines at the top of main:

System.loadLibrary(“test/jogl.dll”);
System.loadLibrary(“test/jogl_awt.dll”);

When I ran the application I got this error:

Exception in thread “main” java.lang.UnsatisfiedLinkError: no test/jogl.dll in java.library.path
at java.lang.ClassLoader.loadLibrary(Unknown Source)
at java.lang.Runtime.loadLibrary0(Unknown Source)
at java.lang.System.loadLibrary(Unknown Source)
at mainpack.GameName.main(GameName.java:6)

How can this be fixed? Notice that I don’t want the user to have to pass any application arguments to the jar file, I want him to be able to run it just from double clicking the jar file.

In case this is a dead end, can somebody please post a sample .jnlp file that launches the jar with the correct binaries?

You could write yourself a laucher-app, that extracts the platform specific files and calls java again with the appropriate commandline arguments.

loadLibrary doesn’t take a path, it takes the library name. The library name is translated into a file name differently, for example on windows jogl becomes jogl.dll, but on mac it’s libjogl.jnilib. All of this conversion is automatic. I’m pretty sure that a folder can’t be part of the library name.

There are two options to solve this:

  1. use loadLibrary and add /test/ to your java.library.path (which should be configurable in the MANIFEST file of your jar).
  2. use System.load() and System.mapLibraryName():

String libName = "jogl";
String libFile = System.mapLibraryName(libName);
System.load("test/" + libFile);

These options are based on the assumption that it’s not a problem loading the native libraries behind JOGL’s back.

http://forums.sun.com/thread.jspa?threadID=5370285

How do you set the manifest’s library path? cause they said you can’t.

That’s a good point, I should stop giving advice without trying it first.

Anyway, here’s blog that should help solve your problem. Read down to the last comment with the large code post.
http://www.javalobby.org/forums/thread.jspa?threadID=15512&tstart=0

There might be a simpler way to do it by invoking the main-method via reflection:


(This is for JInput, but might apply to JOGL as well)

I thought of a solution myself that works rather well. Don’t know why I didn’t think of it sooner; I just check which binaries are needed, then I copy them into the root directory (where the jar is). “.” seems to always be part of the “java.library.path”.

Here’s my solution:


/* Imports used for this. */
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;

/* Code to be placed in the application's main method. */
try {
    String[] binaries = {"win/64/jogl.dll", "win/64/jogl_awt.dll"};
    for (int i=0; i<binaries.length; i++) {
        File srcFile = new File("./binaries/" + binaries[i]);
        File dstFile = new File("./" + binaries[i]
                .substring(binaries[i].lastIndexOf('/')));
        if (!dstFile.exists()) {  //safety - keep existing files
            dstFile.createNewFile();
            FileChannel source = null;
            FileChannel destination = null;
            source = new FileInputStream(srcFile).getChannel();
            destination = new FileOutputStream(dstFile).getChannel();
            destination.transferFrom(source, 0, source.size());
            source.close();
            destination.close();
        }
    }
} catch (Exception e) {
    System.err.println("Error (insert explanation of what has gone " +
            "wrong here and show instructions to the user so that he " +
            "can do the job manually).");
    System.exit(1);
}

The code will be placed at the top of main() and what it does is copying two DLL files to the same folder as the jar (from inside a “binaries” folder that rests in the same path as the jar).

I’ve tried making the code clean so that it will be easy to read, use and understand, that way other people who find this topic can just copy it and be good to go. I’ve read by the way that the way I’m copying files should be really effective.)

What would be great now is a way to compress the binaries. Maybe hide them inside the jar file and then extract them? Is there a way of doing that without making it fail on a lot of computers due to security issues (for example in schools)? Otherwise is it any way of compressing them into their own archives somehow and then extract them?

“jogl.dll” and “jogl_awt.dll” takes 328 KiB uncompressed, and if that is how it is for many other system binaries as well the inclusion of all libraries will easily add over 1 MiB of unnecessary space. By using WinRAR’s best compression method I’m able to reduce the two DLL files from 328 KiB to only 26.7 KiB.

An additional thought, can the running application launch other jars with arguments? Maybe even arguments to the Java Virtual Machine? If so, I can just have main() check which OS it is running on and then start the same jar file again with the “-Djava.library.path=…” argument, adding the correct binaries folder that way. Right? (An additional argument would be passed on to the jar so that main skips the OS check and proceeds to launch the game.)

Please try to answer all questions in this post if you can, they are all important. :slight_smile:

(Note that this topic is NOT to be considered over yet just because I said “here’s my solution” at the beginnig of the post, hehe.)

Edit: Clarified some things.

Nope. This is only true for windows, because windows adds the current working directory implicitely to the PATH and under windows the default java.library.path contains the PATH environment.
Also this might fail (just speculating) if you have static references to JOGL code in your main class.

Best solution currently seems to be to create an extra laucher class without references to any class, and have a main like this:


public static void main(String[] args)
{
    try
    {
        // implementation of the evalPlatform() method left to the student for exercise ;)
        String platform = evalPlatform();

        // Set the java.library.path first
        System.setProperty("java.library.path", System.getProperty("user.dir") + System.getProperty("file.separator") + platform);

        // Get the reference to the actual app class after setting java.library.path to make sure, no subsystem calls loadLibrary too soon
        final Method appmain = Class.forName("your.MainClass").getMethod("main", new Class[]{String[].class});

        // Don't know why the original author of this snippet copies the arguments to a new array, but hey - let's do it anyway...
        final String[] argz = new String[args.length]; 
        System.arraycopy(args, 0, argz, 0, argz.length);

        // Now start your app
        appmain.invoke(null, new Object[]{argz});
    }
    catch (Exception e)
    {
        System.err.println("Error (insert explanation of what has gone " +
            "wrong here and show instructions to the user so that he " +
            "can do the job manually).");
         System.exit(1);
    }
}

Oh, that’s too bad. I’ll try your suggestion soon.

In the mean time, anybody know if a jar file can be launched from inside the main method with program arguments and/or VM arguments?

Also, is it possible to extract files from a jar? So that the binaries can be compressed.

You can simply call the main()-Method of a class with the arguments.

If the jar is not part of your classpath, you could use an URLClassloader with the jar as resource and use classLoaderInstance.loadClass(“your.MainCLass”).invoke(null, new String[]{“arg1”,“arg2”}). You can even read out the jars manifest and get the Main-Class attribute to get the name of the class. You can’t pass system properties in the call - you would have to use System.setProperty() before the invoke call.

Yes it is :slight_smile: see the javadoc for the JarFile class.

The most problematic part might be to reliable get hold of the location of the jar file, since you have no way of getting the location of your own jar and the user.dir system property and relative paths only work, if your jar is started from within the directory where it is located. So with a “java -jar /absulute/path/to/jar” you are screwed.

Tested your suggestion now, it did not work.

I have experienced with Runtime.getRuntime().exec() and found out something that works, but not unless I start the jar file from Window’s cmd terminal. Very strange.

@Your first answer, that does not work. You can’t for example send “-Djava.class.path=binaries” to the virtual machine that way. With the exec() method however I have found out that you can, but it only works from the cmd for some reason.

@Your second answer, I rather have someone that knows to tell me. It’s much better since you only get the information relevant and saves me the trouble of reading page after page after page.

I have a way of getting the jar file that is being executed:

File jarFile = new File(Global.class.getProtectionDomain().getCodeSource().getLocation().toURI());

So if you wish to for example get the file size of the current jar that is being executed:

if (jarFile.isFile()) {long jarFileSize = jarFile.length();}

Sorry for the hasty reply, I have to travel and I’m a bit stressed at the moment.

What was the problem? Are you sure, the path you set with System.setProperty() was correct?

You probably have to pass the absulute path ti the java.exe for exec to make it work correctly. Problem is how to find this.

Right, you can only pass the normal application arguments, not the jvm ones.

This wasn’t meant as a rtfm rant. I just thought you would have missed this class.

Also it’s sometimes hard (or cumbersome) to explain details in a forum from the head. Even if I would have provided you with some pseudo code, you would probably have needed to read the doc to get it to work. And reading the javadoc of the classes you will use doesn’t hurt :wink:

Never thought of that. Thanks for the hint!

I tested my own suggestion and unforunately - as stated - it doesn’t work. After that I tried the method from the javalobby forum, that lhkbob posted and this does indeed work :slight_smile: It might be vendor-specific (sun vm only!?) and not future proof, but it should suffice for now.

Here is my working Launcher class (the Launcher might not be needed, but this way it is nicely separated and maybe I extend this to a generic wrapper):


import java.lang.reflect.Field;

/**
 *
 * @author cylab
 */
public class Launcher
{
    public static void main(String[] args)
    {
        try
        {
            // Get the system paths field, make it accessible and to null
            // so that whenever "System.loadLibrary" is called,
            // it will be reconstructed with the changed value.
            Field sys_paths = ClassLoader.class.getDeclaredField("sys_paths");
            sys_paths.setAccessible(true);
            sys_paths.set(ClassLoader.class, null);
            // Now change the System property
            System.setProperty("java.library.path", 
                "/home/cylab/.netbeans/6.5/jogl-runtime/jogl.jar-natives-linux-i586:/home/cylab/.netbeans/6.5/gluegen-runtime/gluegen-rt.jar-natives-linux-i586");

            // Get the reference to the actual app class after setting java.library.path and start your app
            Class.forName("org.yourorghere.SimpleJOGL")
                .getMethod("main", new Class[]{String[].class}).invoke(null, new Object[]{args});

        }
        catch (Exception e)
        {
            e.printStackTrace();
            System.exit(1);
        }
    }
}

You will still need some code to get the right platform library path.

Looks interesting, I’ll try it now.

Good news everyone, it worked! Now we have a way of changing the java.library.path programatically from inside main(). Setting ClassLoader.class to null really did the trick. Thank you cylab!

Here’s my version of your code.


public class TheMainClass {
    
    public static void main(String[] args) {
        if (args.length==0) {
            try {
                Field sys_paths = ClassLoader.class
                        .getDeclaredField("sys_paths");
                sys_paths.setAccessible(true);
                sys_paths.set(ClassLoader.class, null);
                System.setProperty("java.library.path", "binaries/win32");
                String[] arguments = {"start"};
                TheMainClass.class.getMethod("main", String[].class)
                        .invoke(null, new Object[] {arguments});
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        } else {
            //do whatever main should really do here
        }
    }
}

If an exception occurs one can explain to the user that he can bypass this if he starts the game with the an argument (should he want to aquire the jogl binaries manually and place them in the java.library.path on his system).

As you can see this code makes it impossible to send arguments to the application, if that is needed the code needs to be rewritten a little. It’s easily done but I wanted to keep the code simple and clean for now, this way only one main() method is needed in the application.

What the code does is that it sets the java.library.path to “binaries\win32” - the “binaries” folder is in the same folder as the jar file and the “win32” folder is inside the “binaries” folder (duh).

Remember: If you change the class name from “TheMainClass” to something else you also have to change “TheMainClass” to the same thing a few rows down into the code.

This code was tested on Windows Vista 64, anyone else care to test on other systems? Some of you may notice that I’m loading the win32 binaries on a 64 bit operating system, I guess its the type of Java Virtual Machine that counts and not the OS, I have the 32bit VM installed.

By the way, “/” should be pretty universal right? Would be nice not having to use System.getProperty(“file.separator”) all the time.

Alright, great job everyone. :slight_smile: Now it’s just a matter of figuring out which binaries to use (and compress them if possible). Any suggestions?

You won’t need to call the main method again via reflection with the javalobby version. I just combined both approaches to get a separation of the launcher and the normal main class. If you don’t want that, you can simply use:


public class TheMainClass {
    
    public static void main(String[] args) {
        if (System.getProperty("suppress-natives-injection",null)==null) {
            try {
                Field sys_paths = ClassLoader.class
                        .getDeclaredField("sys_paths");
                sys_paths.setAccessible(true);
                sys_paths.set(ClassLoader.class, null);
                System.setProperty("java.library.path", "binaries/win32");
            } catch (Exception e) {
                e.printStackTrace();
                System.exit(1);
            }
        }
         //do whatever main should really do here
    }
}

This way you can still have normal arguments. On an exception you could advice the user to call the app with


java -Dsuppress-natives-injection -Djava.library.path=/users/own/natives/path YourMainClass

Why that’s just splendid! Nice to see that you don’t have to trick Java by calling main a second time in order to change “java.library.path”.

Now I’m (again) wondering if it’s possible to launch a java application from inside another java application (a solution that always works). Here’s why:

I was thinking about having one java application that asks the user what operating system he is on. Then he presses “start server” or “start client”, then another java application will be launched with arguments descibing which binaries to use. The point of having one java application launching another would be that if the game crashes the launcher application would still be running and can display messages etc about it.

The problem with using exec() would be, I guess, that some systems won’t have just “java” bound to the whole path to java.exe. I’ve seen that once on the University. There’s also something about allowing a java application to launch other java applications that screams security warning to me, making me guess that some places will not allow this thus making the game unable to start.

Thanks for all the help, I hope there’s a solution to this also.

I’ve been wondering by the way… do Jogl draw things outside the area defined by glOrtho? If I draw a circle, but half of it is outside the ortho area, is only the half showing drawn or is the rest also drawn? How about if the whole circle is outside the ortho area, is it not drawn automatically (the commands drawing the circle are ignored) or do I have to implement some kind of bounding box check myself to decide if the commands should be executed or not?

Not drawing stuff outside of view auomatically sounds too good to be true, but I might as well ask anyway. :wink:

The drawing command is not ignored, but nothing would be drawn onscreen (or half-circle etc.). When processing the drawing command, the opengl drivers will clip away things outside of the window. Even though it clips it, it’s better to cull it with the CPU since then the GL commands are never invoked.

Ok, that’s nice. Then I’ll have to somehow find out if the CPU saves processing power if I calculate if a object is worth drawing or not. I guess it’ll only be worth the calculations if very complex things are to be drawn.

Anybody have any thought about the other thing I was wondering about?