OS-dependent library loading

Started by Spezi, January 14, 2007, 17:20:21

Previous topic - Next topic

Matzon

a stripped jre isn't legal yet, though many people dont care about that :)

You basically copy your JRE dir and remove anything that isn't used. Its a trial and error process where you remove some files, and test your app. The actual removal of class files from jre can be automated somewhat, since you can ask the VM to spit out the class names as they're loaded.

Matzon

Quote from: Spezi on January 16, 2007, 08:56:33
What happened to the classloader posted here yesterday? Did an admin delete the post?
As I already mentioned, I saw the post too. I have been unable to locate it in the DB.
So either it was removed accidentally without neither your knowledge nor mine, or there is a bug in SMF. Either way, you will have to repost it :/

Spezi

Quote from: Matzon on January 17, 2007, 08:49:34
Quote from: Spezi on January 16, 2007, 08:56:33
What happened to the classloader posted here yesterday? Did an admin delete the post?

As I already mentioned, I saw the post too. I have been unable to locate it in the DB.
So either it was removed accidentally without neither your knowledge nor mine, or there is a bug in SMF. Either way, you will have to repost it :/

This wasn't my post and unfortunately I cannot remember the author's name. Maybe he deleted it himself.
Ich bin, ich weiß nicht wer.
Ich komme, ich weiß nicht woher.
Ich gehe, ich weiß nicht wohin.
Mich wundert, dass ich so fröhlich bin.

oNyx

Quote from: Eliwood on January 17, 2007, 08:09:05
Stripped JRE and a 5 MB demo? What exactly is "stripped" about this JRE, and how can I do this? If I can bundle a JRE with Stencyl for Windows users without a recent version of Java, that could save me a lot of trouble...

Since you're using swing/awt you won't be able to save that much, but you might be able to get it down to like 5mb jre overhead perhaps.

Well, it's sorta complicated to create such a thing. I used Filemon and ProcessExplorer (both from microsoft), bootstrapping of an extracted jre and lots of regex search n replace over in textpad.

That worked fine for a small demo (the gears demo... 1.6mb in size for everything), but it would be better to use some specialized tool (no one wrote one so far) or a custom jre with a specific set of classes (the absolute minimum + the shizzle you need... like java.util).

Quote from: Matzon on January 17, 2007, 08:47:42
a stripped jre isn't legal yet, though many people dont care about that :)
[...]

LALALA I CANT HEAR YOU! ;)

cornholio

sorry for the befuddlement, guys - the classloader post was by me.

it worked like a charm until i tried an AWT app using 'jgoodies looks':
http://www.jgoodies.com/freeware/looks/

now i was having a strange delay of 5-10 secs when using any combobox
for the first time. this problem occured with WinXP but not with Ubuntu.

anyway ... i decided to delete my post until i would have found time to
resolve this issue and post a working version.

since it might work fine if you don't use strange libraries like i do here is
my initial post again (reconstructed by brain).

does anyone have any idea what could cause the delay with jgoodies ?

---------------------------------------------------------------------------

this could be solved with a custom classloader:

when starting your application supply this JVM-parameter:
-Djava.system.class.loader=foo.CustomClassLoader
(source code below)

this parameter will be consistent across all platforms. the CustomClassLoader will
check which OS it is running on and then load the required native libraries from the
appropriate folder. just modify the values in CustomClassLoader.getLibraryPath()
to match your directory-layout...


package foo;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

public class CustomClassLoader extends URLClassLoader {

	private static final int OS_TYPE_LINUX = 1;

	private static final int OS_TYPE_MACOSX = 2;

	private static final int OS_TYPE_WINDOWS = 3;

	private static final int OS_TYPE;

	static {

		String osName = System.getProperty("os.name");

		if (osName.startsWith("Windows")) {
			OS_TYPE = OS_TYPE_WINDOWS;
		} else if (osName.startsWith("Linux") || osName.startsWith("FreeBSD") || osName.startsWith("SunOS")) {
			OS_TYPE = OS_TYPE_LINUX;
		} else if (osName.startsWith("Mac OS X")) {
			OS_TYPE = OS_TYPE_MACOSX;
		} else {
			throw new LinkageError("Unknown platform: " + osName);
		}
	}

	public CustomClassLoader(ClassLoader parent) {
		super(getClasspath(), null);
	}

	protected String findLibrary(String libName) {

		String[] libNames = getLibraryNames(libName);
		for (int i = 0; i < libNames.length; i++) {
			File libFile = new File(getLibraryPath(), libNames[i]);
			if (libFile.isFile())
				return libFile.getAbsolutePath();
		}

		return super.findLibrary(libName);
	}

	public static final URL[] getClasspath() {

		String[] tk = System.getProperty("java.class.path").split(File.pathSeparator);

		try {

			URL[] urls = new URL[tk.length];
			for (int i = 0; i < urls.length; i++) {
				boolean isJar = tk[i].toLowerCase().endsWith(".jar");
				urls[i] = new URL("file", "localhost", tk[i] + (isJar ? "" : "/"));
			}

			return urls;

		} catch (MalformedURLException e) {
			throw new RuntimeException("malformed classpath");
		}
	}

	public static final String[] getLibraryNames(String libName) {

		switch (OS_TYPE) {
		case OS_TYPE_LINUX:
			return new String[] { "lib" + libName + ".so" };
		case OS_TYPE_MACOSX:
			return new String[] { "lib" + libName + ".dylib", "lib" + libName + ".jnilib" };
		case OS_TYPE_WINDOWS:
			return new String[] { libName + ".dll" };
		default:
			throw new LinkageError("Unknown platform: " + System.getProperty("os.name"));
		}
	}

	public static final String getLibraryPath() {

		switch (OS_TYPE) {
		case OS_TYPE_LINUX:
			return "lib/linux";
		case OS_TYPE_MACOSX:
			return "lib/macosx";
		case OS_TYPE_WINDOWS:
			return "lib/windows";
		default:
			throw new LinkageError("Unknown platform: " + System.getProperty("os.name"));
		}
	}

}

Spezi

Very nice, thank you.
But I still get "UnsatisfiedLinkError: no lwjgl in java.library.path" after setting -Djava.system.class.loader and modifying the paths.

Edit:

Ahh, my mistake, I tried to return "lib/lwjgl/native/win32;lib/qtjava" in the OS_TYPE_WINDOWS case.
With just "lib/lwjgl/native/win32" it works but how can I add more than one path?
Ich bin, ich weiß nicht wer.
Ich komme, ich weiß nicht woher.
Ich gehe, ich weiß nicht wohin.
Mich wundert, dass ich so fröhlich bin.

cornholio

Quote from: Spezi on January 17, 2007, 16:16:11
but how can I add more than one path?

here you go ...


package foo;

import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;


public class CustomClassLoader extends URLClassLoader {

	private static final int OS_TYPE_LINUX = 1;

	private static final int OS_TYPE_MACOSX = 2;

	private static final int OS_TYPE_WINDOWS = 3;

	private static final int OS_TYPE;

	static {

		String osName = System.getProperty("os.name");

		if (osName.startsWith("Windows")) {
			OS_TYPE = OS_TYPE_WINDOWS;
		} else if (osName.startsWith("Linux") || osName.startsWith("FreeBSD") || osName.startsWith("SunOS")) {
			OS_TYPE = OS_TYPE_LINUX;
		} else if (osName.startsWith("Mac OS X")) {
			OS_TYPE = OS_TYPE_MACOSX;
		} else {
			throw new LinkageError("Unknown platform: " + osName);
		}
	}

	public CustomClassLoader(ClassLoader parent) {
		super(getClasspath(), null);
	}

	protected String findLibrary(String libName) {

		String[] libPaths = getLibraryPaths();
		String[] libNames = getLibraryNames(libName);
		for (int p = 0; p < libPaths.length; p++) {
			for (int n = 0; n < libNames.length; n++) {
				File libFile = new File(libPaths[p], libNames[n]);
				if (libFile.isFile())
					return libFile.getAbsolutePath();
			}
		}

		return super.findLibrary(libName);
	}

	public static final URL[] getClasspath() {

		String[] tk = System.getProperty("java.class.path").split(File.pathSeparator);

		try {

			URL[] urls = new URL[tk.length];
			for (int i = 0; i < urls.length; i++) {
				boolean isJar = tk[i].toLowerCase().endsWith(".jar");
				urls[i] = new URL("file", "localhost", tk[i] + (isJar ? "" : "/"));
			}

			return urls;

		} catch (MalformedURLException e) {
			throw new RuntimeException("malformed classpath");
		}
	}

	public static final String[] getLibraryNames(String libName) {

		switch (OS_TYPE) {
		case OS_TYPE_LINUX:
			return new String[] { "lib" + libName + ".so" };
		case OS_TYPE_MACOSX:
			return new String[] { "lib" + libName + ".dylib", "lib" + libName + ".jnilib" };
		case OS_TYPE_WINDOWS:
			return new String[] { libName + ".dll" };
		default:
			throw new LinkageError("Unknown platform: " + System.getProperty("os.name"));
		}
	}

	public static final String[] getLibraryPaths() {

		switch (OS_TYPE) {
		case OS_TYPE_LINUX:
			return new String[] { "lib/linux" };
		case OS_TYPE_MACOSX:
			return new String[] { "lib/macosx" };
		case OS_TYPE_WINDOWS:
			return new String[] { "lib/lwjgl/native/win32", "lib/qtjava" };
		default:
			throw new LinkageError("Unknown platform: " + System.getProperty("os.name"));
		}
	}

}

Spezi

Alright, now I get "NoClassDefFoundError: quicktime/QTException" :D
I don't even get this while starting with the usual method after deleting the additional QTJava.dll from /lib/qtjava.
So what could be wrong there?

Edit:
I also get this if I use -Djava.library.path=lib/qtjava  and the classloader with just "lib/lwjgl/native/win32".
Ich bin, ich weiß nicht wer.
Ich komme, ich weiß nicht woher.
Ich gehe, ich weiß nicht wohin.
Mich wundert, dass ich so fröhlich bin.

cornholio

hm ... seems like there is a problem finding 'quicktime.jar'. i played around
with QTJava and it seems to work for me even with the crappy classloader.

QTJava does some really nasty things in order to find the native libraries. just
check out the sourcecode of 'quicktime.jdirect.JDirectLinker'. for example there
is a native method for loading the native libraries:
private static native int loadNativeLibrary(String s);   ???

Spezi

There is no quicktime.jar. Only QTJava.dll and QTJava.zip.
QTJava.dll is only needed on Windows and QTJava.zip is loaded into eclipse as jar-package.
Ich bin, ich weiß nicht wer.
Ich komme, ich weiß nicht woher.
Ich gehe, ich weiß nicht wohin.
Mich wundert, dass ich so fröhlich bin.

cornholio

you have to rename 'QTJava.zip' to 'quicktime.jar' or 'something.jar'
and make it available on the classpath (e.g. adding it to the build
path in eclipse) ...

Spezi

Quote from: cornholio on January 19, 2007, 12:16:16
you have to rename 'QTJava.zip' to 'quicktime.jar' or 'something.jar'
and make it available on the classpath (e.g. adding it to the build
path in eclipse) ...

OK, I'll try this, but in past it ran perfectly in Windows with -Djava.library.path=lib/lwjgl/native/win32;lib/qtjava.
Ich bin, ich weiß nicht wer.
Ich komme, ich weiß nicht woher.
Ich gehe, ich weiß nicht wohin.
Mich wundert, dass ich so fröhlich bin.

cornholio

you could also change the following line
boolean isJar = tk[i].toLowerCase().endsWith(".jar");


to
         
boolean isJar = (tk[i].toLowerCase().endsWith(".jar") || tk[i].toLowerCase().endsWith(".zip"));

         
if you you want to keep offending a JAR by calling it '.zip' ...  ;)

Spezi

Alright, now it's working as desired. :D
Sorry for the trouble, but since I just used the 2 files from an actual QuickTime installation and that didn't raise any problems I wanted to continue using the files as they are.

Thank you. :)
Ich bin, ich weiß nicht wer.
Ich komme, ich weiß nicht woher.
Ich gehe, ich weiß nicht wohin.
Mich wundert, dass ich so fröhlich bin.

Spezi

Seems I was a bit overhasty. Now there's another problem.
In the release build (not run from eclipse) I get:

Exception in thread "main" java.lang.NoClassDefFoundError: org/lwjgl/opengl/AWTGLCanvas
   at java.lang.ClassLoader.defineClass1(Native Method)
   at java.lang.ClassLoader.defineClass(Unknown Source)
   at java.security.SecureClassLoader.defineClass(Unknown Source)
   at java.net.URLClassLoader.defineClass(Unknown Source)
   at java.net.URLClassLoader.access$100(Unknown Source)
   at java.net.URLClassLoader$1.run(Unknown Source)
   at java.security.AccessController.doPrivileged(Native Method)
   at java.net.URLClassLoader.findClass(Unknown Source)
   at java.lang.ClassLoader.loadClass(Unknown Source)
   at java.lang.ClassLoader.loadClass(Unknown Source)
   at java.lang.ClassLoader.loadClassInternal(Unknown Source)


So the custom classloader is not involved at all although I added -Djava.system.class.loader=game.lwjgl.CustomClassLoader to the call.
Ich bin, ich weiß nicht wer.
Ich komme, ich weiß nicht woher.
Ich gehe, ich weiß nicht wohin.
Mich wundert, dass ich so fröhlich bin.