javamavenjava-native-interfacerxtx

Deploying a self-contained application with RXTX


I have an existing application which normally communicates with it's target over TCP, however new requirements state that the connection may also be made over a serial COM port.

The application as it stands is wholly self-contained, in that is it a single jar file which the end user can copy where it might be needed, and double click to launch.

It seems that RXTX breaks this model, as it requires additional DLL or SO native plugins to be included on the library path; if it is not possible to communicate with Serial ports using native Java, how can I package my application (using ,aven) such that RXTX will be available at runtime, by simply double clicking the Jar file.

java.lang.UnsatisfiedLinkError: no rxtxSerial in java.library.path: [__CLASSPATH__] thrown while loading gnu.io.RXTXCommDriver

Solution

  • You need to package the library into your jar file, then extract it, writing it out as a file, then load it (import statements and exception/error handling omitted):

    public class YourClass {
        static {
            // path to and base name of the library in the jar file
            String libResourcePath = "path/to/library/yourLib.";
    
            // assume Linux
            String extension = "so";
    
            // Check for Windows
            String osName = System.getProperty("os.name").toLowerCase();
            if (osName.contains("win"))
                extension = "dll";
    
            libResourcePath += extension;
    
            // find the library in the jar file
            InputStream is = ClassLoader.getSystemResourceAsStream( libResourcePath );
    
            // create a temp file for the library
            // (and we need the File object later so don't chain the calls)
            File libraryFile = File.getTempFile("libName", extension);
    
            // need a separate OutputStream object so we can
            // explicitly close() it - can cause problems loading
            // the library later if we don't do that
            FileOutputStream fos = new FileOutputStream(libraryFile);
    
            // assume Java 9
            is.transferTo(fos);
    
            // don't forget these - especially the OutputStream
            is.close();
            fos.close();
    
            libraryFile.setExecutable(true);
            libraryFile.deleteOnExit();
    
            // use 'load()' and not 'loadLibrary()' as the
            // file probably doesn't fit the required naming
            // scheme to use 'loadLibrary()'
            System.load(libraryFile.getCanonicalPath());
        }
    
        ...
    }
    

    Note that you'll need to add versions of your library for every OS and architecture you support. That includes both 32- and 64-bit versions, because it's quite possible to run a 32-bit JVM on a 64-bit OS.

    You can use the os.arch system property to identify if you're running in a 64-bit JVM. If the property has the string "64" in it, you're running in a 64-bit JVM. It's pretty safe to assume it's a 32-bit JVM otherwise:

    if (System.getProperty("os.arch").contains("64"))
        // 64-bit JVM code
    else
        // 32-bit JVM code
    

    You might also have to use YourClass.class.getClassLoader().getResourceAsStream() if you're customizing your class loader(s).

    Watch out for OS and CPU compatibility. You need to compile your libraries so they run on older OS versions and older CPUs. If you build on Centos 7 on a new CPU, someone who tries to run on Centos 6 or an older CPU will have problems. You don't want your product crashing on the user because your library got a SIGILL signal for an illegal instruction. I'd recommend building the libraries on VMs where you can control the build environment - create VMs using older CPU models and install old OS versions on them.

    You also need to watch out for Linux systems that mount /tmp with noexec. That or some SE Linux settings might cause the shared object load to fail.