A Maven-based framework for managing the native (JNI) parts of Java codebases. The idea is to include any .dll/.dylib/.so files in a jar as resources, and to load these via resource loading from the classpath. Generalising the approach taken by [Snappy-Java] (https://github.com/xerial/snappy-java), the compiled library for all available platforms is bundled into a single jar. This gives the illusion of platform independence, pure Java code and thus of a regular Maven build/test/install cycle and seamless dependency management of JNI-contaminated artifacts.
The primary artifact for native library loading is under the main directory:
$ cd java-native-loader/main
$ mvn install
A sample Java codebase with JNI parts is at examples/hello-world:
$ cd java-native-loader/examples/hello-world
$ mvn test
The following projects use the Java Native Loader framework described here in order to build and then load their own C code:
-
The [Device Files] (https://github.com/uw-dims/device-files) project enables Java programs to extract low-level disk parameters such as vendor and serial number, plus whole disk size.
-
The [TSK4J] (https://github.com/uw-dims/tsk4j) project provides an object-oriented view on the [Sleuthkit] (http://www.sleuthkit.org/sleuthkit/) host-forensics library.
-
The [FUSE4J] (https://github.com/uw-dims/fuse4j) project implements Java bindings to [FUSE] (http://fuse.sourceforge.net).
These are far better examples than the hello-world example above. DeviceFiles needs different C sources and build procedures for different platforms. TSK4J involves linking to third party C libraries. FUSE4J needs both different build procedures (compiler flags differing in 32 vs 64 bit builds) and links to a third party C library.
The examples/hello-world sample shows how to organize the management of JNI resources in this framework:
-
The [POM] (./examples/hello-world/pom.xml) shows how we use a Maven profile (called native) to drive the C compilation and linking steps. A build without that profile activated results in a regular Java-only Maven build. The POM also uses a set of profiles to 'canonicalize' the local architecture string (result of os.arch) to match that assumed by the main [NativeLoader] (./main/src/main/java/edu/uw/apl/nativelibloader/NativeLoader.java) class.
-
A platform-dependent [Makefile] (examples/hello-world/src/main/native/Linux/x86_64/Makefile) shows how the linked library (in this case an .so file) is transferred to src/main/resources for inclusion in the packaged Maven artifact.
-
The sample [Hello.java] (examples/hello-world/src/main/java/greetings/Hello.java) shows use of the NativeLoader api located in the main artifact (see above).
The C code for the hello-world sample is simple enough that no platform-specific parts are necessary, so we place all the C code in [src/main/native] (examples/hello-world/src/main/native). More complicated C code may require platform and even bit-ness (32 vs 64) sub-components.
We have compiled the JNI parts of the hello-world sample for Linux only (both 64-bit and 32-bit variants). The .so files end up as resources. Nevertheless, the Maven pom file, Makefiles and directory structure promote a many-platform build (Windows, MacOS, etc). The idea is to create platform and bitness (32/64) specific build scripts (e.g. Makefiles for Linux/Mac OS, .proj/NMAKE files for Windows), and build C sources from those. The resulting .so/.dylib/.dll files, once transferred to src/main/resources, are then put under version control, so need building only once per platform. We treat the .so/.dylib/.dll products essentially as source code components, with a Maven profile-driven build of those platform-specific parts. The net effect is that the C parts of a split Java/C build become almost as easy to manage as regular Java classes.
The ideas behind this work were presented at the Seattle Java User Group meeting of 19 May 2015. Slides and video available on the [Seajug web site] (http://www.seajug.org). A local copy of the slides is also included here.