Exporting Java game using Jogl into ONLY one Jar in Eclipse

Hello.

I’m making a simple little test game with Jogl. I have set up the “Java Build Path” so that these two jar files are “Referenced Libraries”:

gluegen-rt.jar
jogl.jar

Then I have downloaded two DLL files and placed them in the project “root” folder (the folder that has the “src” and “bin” folders):

jogl.dll
jogl_awt.dll

With these two DLL files in that folder I am able to “Run” the game from Eclipse. However when I export the game as a “Runnable JAR file” the game will not start when I execute the jar file. If I place the two DLL files in the same folder as the jar file I can however run the game.

My operating system is Vista 64. I have realized those two DLL files are just for my OS. However I want my game to work on Linux also (and any other system that supports OpenGL for that matter, as Java was intended).

I guess it’s possible to put all OpenGL binaries for every system on Earth in a RAR archive, and then include it with my game distribution. However this requires the user to find the correct binaries and place them in the same directory as the jar file. And what if the binaries get old or does not exist?

I have played a few Jogl games from my browser, using Java Web Start (jnlp file). I realized that these games do not require any DLL files to be placed outside of the jar file, and when I downloaded and inspected the jar file the DLL files was not inside it. This is also true for the official Jogl demos at: https://jogl-demos.dev.java.net/

Why can these Java Applications run Jogl??

I want to distribute my game as a single jar file too, I don’t want to have to bundle the neccessary binaries for Jogl.

What’s the secret?

The jnlp files for those applications are setup so that the proper native libraries are downloaded, too. They are not embedded within the jar.

So I have to include the binaries when I release my game?

Can you tell me how I can make a jnlp file that loads the correct binaries depending on if you start the game on linux/windows/other?

I would like to be able to distribute this game folder:

GameName/start.jnlp
GameName/v1.0.jar
GameName/data/linux/
GameName/data/windows/

Then the person that want to play the game just have to double click on “start.jnlp”, then “v1.0.jar” will launch with the correct binaries depending on which operating system he is on.

I’m assuming that I can use a jnlp file even though nothing is supposed to be downloaded (for offline usage).

A jnlp can be run in offline mode, but it must download everything first, so I don’t think this is the route you want. The benefit of using the jnlp would be that only the correct OS natives would be downloaded.

If you’re giving them all of the natives anyway in your folder structure, why not just place all native libraries in the same directory? The JVM is smart enough to only load the ones for its operating system.

Correct me, if I am wrong, but unfortunately that won’t work for all platforms. For example the dlls for win32 and amd64 are named the same and thus cannot be placed in the same directory. Easiest way is to include all native libraries in your distribution and provide different start-scripts for the different platforms.

cylab: Yes I have noticed that problem.

So i guess I’m stuck with having different start scripts for each platform? :frowning:

Or maybe not… it would be great if I can somehow check in the main class which operating system is being run, and then I just set which binaries to use inside the class. Then I will get this distribution:

GameName/GameName_v1.0.jar
GameName/data/linux/
GameName/data/windows/

Which is even better than before because regardless of which operating system is being used only “GameName_v1.0.jar” needs to be run.

So new question, is it possible to tell the Java Virtual Machine which binaries to load inside the main class?

Looking for something like this:

if (windows64bit){
loadThisBinary(“windows/64/blabla.dll”)
} else if (windows32bit) {
loadThisBinary(“windows/32/blabla.dll”)
} else if (linux) {
loadThisBinary(“linux/blabla.dll”)
} else {
//show dialog that the user needs to fix the correct Jogl binaries himself and that without them a exception will be thrown
}

This way the same jar file can be opened on all operating systems and the correct binaries will be loaded automatically without the user being bothered at all. :slight_smile:

Oooh … good point, I didn’t think of that.

This is a pretty annoying issue and should have been solved long time ago from JOGL’s point of view.

In theory yes (System.loadLibrary()) but i fear this won’t work since jogl will try to load the libs again (not sure). Secondly i think the JVM can’t load natives directly from jars, AFAIK library path has to consist of plain folders.

But you could do it the same way like webstart does it. Use one lib folder and copy at first start the platform dependent libs into the path. In fact that what we’ve done in the NetBeans OpenGL Pack on module install (in a jogl independent manner) too.
https://netbeans-opengl-pack.dev.java.net/source/browse/netbeans-opengl-pack/trunk/native-lib-support/src/net/java/nativelibsupport/

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!