Loading OpenCV with System.load("path/to/opencv") fails on Windows, works on Linux

24 Views Asked by At

I am shipping an application that uses OpenCV as a single executable jar. To avoid the end user needing to download OpenCV on their own system, the OpenCV binaries are stored inside the jar, and extracted to a temp file, and loaded using System.load() with the tempfile path, per this stackoverflow post suggestion, as follows:

public class NativeLibraryLoader {

    private static Logger logger = LogManager.getLogger();

    /**
     * This is a safe way to load a native library from a jar, assuming that library
     * is inside your resource director/classpath.
     */
    public static void loadLibraryFromJar(String libraryName) throws IOException {
        logger.debug("Loading native library %s".formatted(libraryName));
        InputStream is = NativeLibraryLoader.class.getResourceAsStream("/" + libraryName);
        File library = File.createTempFile("jni-temp-file", "tmp");
        library.deleteOnExit();
        try (OutputStream os = new FileOutputStream(library)) {
            logger.debug("Copying library input stream to temp file " + library.getPath());
            os.write(is.readAllBytes());
        }
        logger.debug("Loading library " + library.getPath());
        System.load(library.getPath().replace("\\", "/"));
    }

}

This method works just fine on linux, when I am loading opencv_490.so. The library loads, and I am able to use OpenCV. However when running the same command on Windows using opencv_490.dll, it cannot find the library and I get an UnsatisfiedLink. The loadLibraryFromJar method throws the error at the call to System.load(), and produces the following error during the unit test:

[ERROR] Tests run: 2, Failures: 0, Errors: 2, Skipped: 0, Time elapsed: 0.188 s <<< FAILURE! - in com.toughdata.autogrowth.TestOpenCVCaptchaProcessor
[ERROR] testSolveRotateCaptchaIsKnownValue  Time elapsed: 0.187 s  <<< ERROR!
java.lang.UnsatisfiedLinkError: C:\Users\greg\AppData\Local\Temp\jni-temp-file78576686543799615tmp: Can't find dependent libraries
        at com.toughdata.autogrowth.TestOpenCVCaptchaProcessor.testSolveRotateCaptchaIsKnownValue(TestOpenCVCaptchaProcessor.java:16)

[ERROR] testSolvePuzzleCaptchaIsKnownValue  Time elapsed: 0 s  <<< ERROR!
java.lang.NoClassDefFoundError: Could not initialize class com.toughdata.autogrowth.automation.captcha.OpenCVCaptchaProcessor
        at com.toughdata.autogrowth.TestOpenCVCaptchaProcessor.testSolvePuzzleCaptchaIsKnownValue(TestOpenCVCaptchaProcessor.java:24)
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.UnsatisfiedLinkError: C:\Users\greg\AppData\Local\Temp\jni-temp-file78576686543799615tmp: Can't find dependent libraries [in thread "main"]
        at com.toughdata.autogrowth.TestOpenCVCaptchaProcessor.testSolveRotateCaptchaIsKnownValue(TestOpenCVCaptchaProcessor.java:16)

[INFO]
[INFO] Results:
[INFO]
[ERROR] Errors:
[ERROR]   TestOpenCVCaptchaProcessor.testSolvePuzzleCaptchaIsKnownValue:24 NoClassDefFound
[ERROR]   TestOpenCVCaptchaProcessor.testSolveRotateCaptchaIsKnownValue:16 » UnsatisfiedLink
[INFO]
[ERROR] Tests run: 19, Failures: 0, Errors: 2, Skipped: 8
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  13.035 s
[INFO] Finished at: 2024-03-11T09:04:46+11:00
[INFO] ------------------------------------------------------------------------

I can see that the tempfile is created, and that the library is extracted and written to the tempfile. Does anyone know what could cause the call to System.load() to fail on windows when it works fine on Linux?

1

There are 1 best solutions below

0
gbiz123 On

Alright, I was able to figure this out. I went out on a limb and changed the tmp extension to .dll when creating the temp file. It turns out that Windows expects the libraries to end with the .dll file extension. Linux does not care whether your libraries end with .so or not, but on windows they must end with .dll

The correct code for using System.load() to laod a dll from a jar on windows is as follows:

public class NativeLibraryLoader {

    private static Logger logger = LogManager.getLogger();

    /**
     * This is a safe way to load a native library from a jar, assuming that library
     * is inside your resource director/classpath.
     */
    public static void loadLibraryFromJar(String libraryName) throws IOException {
        logger.debug("Loading native library %s".formatted(libraryName));
        InputStream is = NativeLibraryLoader.class.getResourceAsStream("/" + libraryName);
        File library = File.createTempFile("jni-temp-file", ".dll");
        library.deleteOnExit();
        try (OutputStream os = new FileOutputStream(library)) {
            logger.debug("Copying library input stream to temp file " + library.getPath());
            os.write(is.readAllBytes());
        }
        logger.debug("Loading library " + library.getPath());
        System.load(library.getPath().replace("\\", "/"));
    }

}