views:

135

answers:

1

I'm dynamically loading a class and calling a method on it. This class does JNI. When I call the class, java attempts to load the library. This causes an error because the library is not on the libpath. I'm calling from instead a jar so I can't easily change the libpath (especially since the library is not in the same directory or a sub directory of the jar). I do know the path of the library, but how can I load it before I load the class.

Current code:

public Class<?> loadClass(String name) throws ClassNotFoundException {
    if(!CLASS_NAME.equals(name))
            return super.loadClass(name);

    try {
        URL myUrl = new URL(classFileUrl);
        URLConnection connection = myUrl.openConnection();
        InputStream input = connection.getInputStream();
        byte[] classData = readConnectionToArray(input);

        return defineClass(CLASS_NAME,
                classData, 0, classData.length);

    } catch (MalformedURLException e) {
        throw new UndeclaredThrowableException(e);
    } catch (IOException e) {
        throw new UndeclaredThrowableException(e); 
    }
}

Exception:

Can't find library libvcommon.so
java.lang.UnsatisfiedLinkError: vcommon (A file or directory in the path name does not exist.)
        at java.lang.ClassLoader.loadLibraryWithPath(ClassLoader.java:998)
        at java.lang.ClassLoader.loadLibraryWithClassLoader(ClassLoader.java:962)
        at java.lang.System.loadLibrary(System.java:465)
        at vcommon.(vcommon.java:103)
        at java.lang.J9VMInternals.initializeImpl(Native Method)
        at java.lang.J9VMInternals.initialize(J9VMInternals.java:200)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
        at java.lang.reflect.Method.invoke(Method.java:599)
        at com.fortune500.fin.v.vunit.reflection.ReflectionvProcessor.calculateV(ReflectionvProcessor.java:36)
        at com.fortune500.fin.v.vunit.UTLTestCase.execute(UTLTestCase.java:42)
        at com.fortune500.fin.v.vunit.TestSuite.execute(TestSuite.java:15)
        at com.fortune500.fin.v.vunit.batch.Testvendor.execute(Testvendor.java:101)
        at com.fortune500.fin.v.vunit.batch.Testvendor.main(Testvendor.java:58)

Edit: I'm having a 64bit vs 32bit issue right now. I'll come back to this when I've sorted that out.

Related: Dynamic loading a class in java with a different package name

+1  A: 

If you know the path to the library, you could add the path to the java.library.path environment variable in your custom class loader. A simpler approach is to compute the path and use it in your call to Runtime.loadLibrary.

The code below outlines two approaches, using loadLibrary and setting the java.library.path system property.

 if(CLASS_NAME.equals(name)) {

    // two ways of doing this - either load the library explicitly from the full path  
    if (useFullPath) {
      Runtime.getRuntime().loadLibrary("/full/path/to/mylibrary");
    }
    else { // or tweaking the library path
      System.setProperty("java.library.path", 
         System.getProperty("java.library.path")
         + System.getProperty("file.separator")
         + "/path/to/lib");
    }
 }
 return super.loadClass(name);

You mention your code is being called from a jar - using a custom class loader is going to be difficult if the ClassLoader is part of the jar as well. Have you verified that your class loader is indeed being used?

A simpler approach is to change the current call to loadLibrary in your native class to use the full path. E.g. fetch from system properties, or compute it, if you know in advance where to find it. This is only an option of course if you have the source to the native class. If you can't modify the native class, then use the loadLibrary call in the classloader.

It's my understanding that calls to load library with the same library name (regardless of path) load the same library. (At least, that's the behaviour on Windows - I haven't verified on Linux.) So, even though the classloader loads the library using the full path, and the native class loads the library using it's simple name, both should resolve to the same library.

(Just for completeness, resolving equivalent libraries happens in the kernel, again, speaking from Win32 experience. Each library internally has a name, and windows only loads one instance of the library with the same internal name per process.)

mdma
I tried the loadLibrary(fullPath) method, but that's throwing a File Not Found exception.
C. Ross
make sure you leave off the file extension
mdma
@mdma Thanks for pointing that out!
C. Ross
Sorry I forgot to mention it before, must have got lost in the zilions of other picodetails that we developers have to keep in our heads. :-) Did it help?
mdma
@mdma Neither method is working for me right now. I also found my lib path *already* contains the appropriate directory. `Runtime.load` fails with the full path, or the full path minus the extension. The `UnsatisfiedLinkError` is always "(A file or directory in the path name does not exist.)" I have checked the full path, even from the program right before the `load` call, and the file definitely exists.
C. Ross
Does the library itself need any dependencies? If they are not available, that will also lead to a unsatisfied link error.
mdma
@mdma, Just figured it out. The library is 32 bit, so it would need to be re-compiled. It's a horrible error message though. Thanks a lot IBM (it's an IBM Aix JVM).
C. Ross
I'm glad that you found the problem. Platform incompatibility was going to be my next suggestion, if it wasn't dependencies. (32-bit it still the dominant platform, but I was going to ask just to cover all the bases.)
mdma