Load Native Library from Class path

前端 未结 3 1530
孤独总比滥情好
孤独总比滥情好 2020-12-06 20:03

I have a project setup that follows the Standard Directory Layout (not using Maven though):

src/main
      | java
      | resources
         | library.dll
<         


        
相关标签:
3条回答
  • 2020-12-06 20:10

    I have done something very similar in JNativeHook, you will need some helper code to determine the correct arch and os to load code for (See NativeSystem Class)

    // The following code covered under the GNU Lesser General Public License v3.
    static {
        String libName = System.getProperty("jnativehook.lib.name", "JNativeHook");
    
        try {
            // Try to load the native library assuming the java.library.path was
            // set correctly at launch.
            System.loadLibrary(libName);
        }
        catch (UnsatisfiedLinkError linkError) {
            // Get the package name for the GlobalScreen.
            String basePackage = GlobalScreen.class.getPackage().getName().replace('.', '/');
    
            // Compile the resource path for the native lib.
            StringBuilder libResourcePath = new StringBuilder("/");
            libResourcePath.append(basePackage).append("/lib/");
            libResourcePath.append(NativeSystem.getFamily()).append('/');
            libResourcePath.append(NativeSystem.getArchitecture()).append('/');
    
    
            // Get what the system "thinks" the library name should be.
            String libNativeName = System.mapLibraryName(libName);
            // Hack for OS X JRE 1.6 and earlier.
            libNativeName = libNativeName.replaceAll("\\.jnilib$", "\\.dylib");
    
            // Slice up the library name.
            int i = libNativeName.lastIndexOf('.');
            String libNativePrefix = libNativeName.substring(0, i) + '-';
            String libNativeSuffix = libNativeName.substring(i);
            String libNativeVersion = null;
    
            // This may return null in some circumstances.
            InputStream libInputStream = GlobalScreen.class.getResourceAsStream(libResourcePath.toString().toLowerCase(Locale.English) + libNativeName);
            if (libInputStream != null) {
                try {
                    // Try and load the Jar manifest as a resource stream.
                    URL jarFile = GlobalScreen.class.getProtectionDomain().getCodeSource().getLocation();
                    JarInputStream jarInputStream = new JarInputStream(jarFile.openStream());
    
                    // Try and extract a version string from the Manifest.
                    Manifest manifest = jarInputStream.getManifest();
                    if (manifest != null) {
                        Attributes attributes = manifest.getAttributes(basePackage);
    
                        if (attributes != null) {
                            String version = attributes.getValue("Specification-Version");
                            String revision = attributes.getValue("Implementation-Version");
    
                            libNativeVersion = version + '.' + revision;
                        }
                        else {
                            Logger.getLogger(GlobalScreen.class.getPackage().getName()).warning("Invalid library manifest!\n");
                        }
                    }
                    else {
                        Logger.getLogger(GlobalScreen.class.getPackage().getName()).warning("Cannot find library manifest!\n");
                    }
                }
                catch (IOException e) {
                    Logger.getLogger(GlobalScreen.class.getPackage().getName()).severe(e.getMessage());
                }
    
    
                try {
                    // The temp file for this instance of the library.
                    File libFile;
    
                    // If we were unable to extract a library version from the manifest.
                    if (libNativeVersion != null) {
                        libFile = new File(System.getProperty("java.io.tmpdir"), libNativePrefix + libNativeVersion + libNativeSuffix);
                    }
                    else {
                        libFile = File.createTempFile(libNativePrefix, libNativeSuffix);
                    }
    
                    byte[] buffer = new byte[4 * 1024];
                    int size;
    
                    // Check and see if a copy of the native lib already exists.
                    FileOutputStream libOutputStream = new FileOutputStream(libFile);
    
                    // Setup a digest...
                    MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
                    DigestInputStream digestInputStream = new DigestInputStream(libInputStream, sha1);
    
                    // Read from the digest stream and write to the file steam.
                    while ((size = digestInputStream.read(buffer)) != -1) {
                        libOutputStream.write(buffer, 0, size);
                    }
    
                    // Close all the streams.
                    digestInputStream.close();
                    libInputStream.close();
                    libOutputStream.close();
    
                    // Convert the digest from byte[] to hex string.
                    String sha1Sum = new BigInteger(1, sha1.digest()).toString(16).toUpperCase();
                    if (libNativeVersion == null) {
                        // Use the sha1 sum as a version finger print.
                        libNativeVersion = sha1Sum;
    
                        // Better late than never.
                        File newFile = new File(System.getProperty("java.io.tmpdir"), libNativePrefix + libNativeVersion + libNativeSuffix);
                        if (libFile.renameTo(newFile)) {
                            libFile = newFile;
                        }
                    }
    
                    // Set the library version property.
                    System.setProperty("jnativehook.lib.version", libNativeVersion);
    
                    // Load the native library.
                    System.load(libFile.getPath());
    
                    Logger.getLogger(GlobalScreen.class.getPackage().getName())
                            .info("Library extracted successfully: " + libFile.getPath() + " (0x" + sha1Sum + ").\n");
                }
                catch (IOException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
                catch (NoSuchAlgorithmException e) {
                    throw new RuntimeException(e.getMessage(), e);
                }
            }
            else {
                Logger.getLogger(GlobalScreen.class.getPackage().getName())
                        .severe("Unable to extract the native library " + libResourcePath.toString().toLowerCase(Locale.English) + libNativeName + "!\n");
    
                throw new UnsatisfiedLinkError();
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-06 20:12

    There is an old-time hack that still works as of today ( 1.7.0_55 & 1.8.0_05 ) to allow you to do to a runtime update using System.setProperty() and have the JVM notice the change. In general, you do the following:

    System.setProperty("java.library.path", yourPath);
    Field sysPath = ClassLoader.class.getDeclaredField( "sys_paths" );
    sysPath.setAccessible( true );
    sysPath.set( null, null );
    System.loadLibrary(libraryName);
    

    Google java sys_paths and look for articles about this technique.

    Take care to handle errors/exceptions. Restore original paths as needed.

    0 讨论(0)
  • 2020-12-06 20:25

    I wanted to offer an alternative solution, because @Java42's answer is not portable (JVM dependant) and doesn't work starting from Oracle's/OpenJDK's Java 12.

    You should use your own custom ClassLoader implementation. In the ClassLoader there's a method findLibary(String libname). This method returns the full path to the library to load. This can be used to load libraries in arbitrary places.

    public class MyClassLoader extends ClassLoader {
        @Override
        protected String findLibrary(String libname) {
            return "/actual/path/to/library.so";
            // or return null if unknown, then the path will be searched
        }
    }
    

    Next step is to have you custom ClassLoader used by the JVM. So set it very soon in your code as the current threads contextClassLoader:

        public static void main(String[] args) {
            Thread.currentThread().setContextClassLoader(new StarterClassLoader());
            // ... your code
        }
    
    0 讨论(0)
提交回复
热议问题