Hello Everyone,
I'm having a really hard time writing and using my own ClassLoader in a Java Applet.
Context :
As the this link shows - http://codethesis.com/tutorial.php?id=1 - loading and especially unloading native libraries through Java requires defining our own ClassLoader, and use it to instantiate a class loading a library. When the class using native libraries has finished execution, setting all references to the classloader and calling the garbage collector will cause the native library to be unloaded. The class to load within the custom classloader is thus read byte after byte from the jar and defined using the Classloader.defineClass(..) function. So that's what I did. But I've got two problems.
Problem 1 :
On one single machine over 15 tested, the magic number of a given class read from the Jar using Applet.class.getResourceAsStream(classname) takes a value different from CAFEBABE and the defineClass function then throws an "Incompatible magic value" exception (see below). The workaround I found is to force the first 4 bytes of the byte array read from the class with CAFEBABE. But I still would like to understand why it takes a different value on this machine.
Exception in thread "thread applet-MyApplet.class-1" java.lang.ClassFormatError: Incompatible magic value 409165630 in class file Reader
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at NativeClassLoader.findClass(Unknown Source)
....
Problem 2 :
On windows, the NativeClassLoader works perfectly, but on Linux, I'm getting a java.lang.VerifyError (see below).
Code is compiled with java 1.6.0_06 on windows XP. I tried to remove everything related to native code (remove .so load), the same error is raised.
java.lang.VerifyError: (class: Reader, method: <clinit> signature: ()V) Illegal instruction found at offset 1
at java.lang.Class.getDeclaredConstructors0(Native Method)
at java.lang.Class.privateGetDeclaredConstructors(Class.java:2389)
at java.lang.Class.getConstructor0(Class.java:2699)
at java.lang.Class.newInstance0(Class.java:326)
at java.lang.Class.newInstance(Class.java:308)
...
Code :
NativeClassReader (custom) :
public class NativeClassLoader extends ClassLoader {
//the unique instance of the NativeClassLoader
private static NativeClassLoader instance;
private NativeClassLoader () {
super(NativeClassLoader.class.getClassLoader());
}
/**
* Get the Singleton instance of the class
*/
public static NativeClassLoader getInstance () {
if (instance == null)
instance = new NativeClassLoader();
return instance;
}
public static void dispose () {
instance = null;
}
/**
* Load a class using its full java name (prefixed with package)
*/
public Class findClass (String theName) {
byte[] b = null;
try {
b = loadClassDataFromJar(theName);
Class clazz = defineClass(theName, b, 0, b.length);
resolveClass(clazz);
return clazz;
} catch (Exception e) {
return null;
}
}
/**
* Gets the bytes of a class file stored in the current jar using
* its full class name
*/
public byte[] loadClassDataFromJar (String theName)
throws Exception {
String filename = "/" + theName.replace('.', '/') + ".class";
InputStream is = SawsApplet.class.getResourceAsStream(filename);
BufferedReader br = new BufferedReader(new InputStreamReader(is));
//compute file size
Vector vChars = new Vector();
int c;
while ((c = br.read()) != -1)
vChars.add(new Byte((byte) c));
//fill in byte array with chars read from the buffer
byte[] buff = new byte[vChars.size()];
//workaround for a bug on one (some) Vista machine(s)
//force magic number to CAFEBABE instead of 18635F3E
if (vChars.size() > 3) {
buff[0] = (byte) 0xCA;
buff[1] = (byte) 0xFE;
buff[2] = (byte) 0xBA;
buff[3] = (byte) 0xBE;
}
for (int i = 4; i < vChars.size(); ++i)
buff[i] = ((Byte) vChars.get(i)).byteValue();
return buff;
}
}
Reader (loading native libary) :
public class Reader {
static {
System.loadLibrary("myLib");
}
public static native String getData();
}
Main :
NativeClassLoader cLoader = NativeClassLoader.getInstance();
Class clazz = cLoader.findClass("Reader"); // ClassFormatError thrown here
Object reader = clazz.newInstance(); // VerifyError thrown here
Method m = clazz.getMethod("getData");
String s = m.invoke(reader);
print(s);
s = null;
m = null;
reader = null;
clazz = null;
cLoader = null;
NativeClassLoader.dispose();
System.gc
Any ideas would be really appreciated :-)
Guillaume