diff --git a/.cproject b/.cproject new file mode 100644 index 00000000..3d5817da --- /dev/null +++ b/.cproject @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + make + + TestJNI.h + true + true + true + + + make + + all + true + true + true + + + make + + clean + true + true + true + + + + diff --git a/src/main/java/cz/adamh/utils/NativeUtils.java b/src/main/java/cz/adamh/utils/NativeUtils.java new file mode 100644 index 00000000..fab5db0c --- /dev/null +++ b/src/main/java/cz/adamh/utils/NativeUtils.java @@ -0,0 +1,145 @@ +/* + * Class NativeUtils is published under the The MIT License: + * + * Copyright (c) 2012 Adam Heinrich + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package cz.adamh.utils; + +import java.io.*; +import java.nio.file.FileSystemNotFoundException; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.ProviderNotFoundException; +import java.nio.file.StandardCopyOption; + +/** + * A simple library class which helps with loading dynamic libraries stored in the + * JAR archive. These libraries usualy contain implementation of some methods in + * native code (using JNI - Java Native Interface). + * + * @see http://adamheinrich.com/blog/2012/how-to-load-native-jni-library-from-jar + * @see https://github.com/adamheinrich/native-utils + * + */ +public class NativeUtils { + + /** + * The minimum length a prefix for a file has to have according to {@link File#createTempFile(String, String)}}. + */ + private static final int MIN_PREFIX_LENGTH = 3; + + /** + * Temporary directory which will contain the DLLs. + */ + private static File temporaryDir; + + /** + * Private constructor - this class will never be instanced + */ + private NativeUtils() { + } + + /** + * Loads library from current JAR archive + * + * The file from JAR is copied into system temporary directory and then loaded. The temporary file is deleted after + * exiting. + * Method uses String as filename because the pathname is "abstract", not system-dependent. + * + * @param path The path of file inside JAR as absolute path (beginning with '/'), e.g. /package/File.ext + * @throws IOException If temporary file creation or read/write operation fails + * @throws IllegalArgumentException If source file (param path) does not exist + * @throws IllegalArgumentException If the path is not absolute or if the filename is shorter than three characters + * (restriction of {@link File#createTempFile(java.lang.String, java.lang.String)}). + * @throws FileNotFoundException If the file could not be found inside the JAR. + */ + public static void loadLibraryFromJar(String path) throws IOException { + + if (!path.startsWith("/")) { + throw new IllegalArgumentException("The path has to be absolute (start with '/')."); + } + + // Obtain filename from path + String[] parts = path.split("/"); + String filename = (parts.length > 1) ? parts[parts.length - 1] : null; + + // Check if the filename is okay + if (filename == null || filename.length() < MIN_PREFIX_LENGTH) { + throw new IllegalArgumentException("The filename has to be at least 3 characters long."); + } + + // Prepare temporary file + if (temporaryDir == null) { + temporaryDir = createTempDirectory("nativeutils"); + temporaryDir.deleteOnExit(); + } + + File temp = new File(temporaryDir, filename); + + try (InputStream is = NativeUtils.class.getResourceAsStream(path)) { + Files.copy(is, temp.toPath(), StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + temp.delete(); + throw e; + } catch (NullPointerException e) { + temp.delete(); + throw new FileNotFoundException("File " + path + " was not found inside JAR."); + } + + try { + System.load(temp.getAbsolutePath()); + } finally { + if (isPosixCompliant()) { + // Assume POSIX compliant file system, can be deleted after loading + temp.delete(); + } else { + // Assume non-POSIX, and don't delete until last file descriptor closed + temp.deleteOnExit(); + } + } + } + + private static boolean isPosixCompliant() { + try { + if (FileSystems.getDefault() + .supportedFileAttributeViews() + .contains("posix")) { + return true; + } + return false; + } catch (FileSystemNotFoundException + | ProviderNotFoundException + | SecurityException e) { + return false; + } + } + + private static File createTempDirectory(String prefix) throws IOException + { + String tempDir = System.getProperty("java.io.tmpdir"); + File generatedDir = new File(tempDir, prefix + System.nanoTime()); + + if (!generatedDir.mkdir()) + throw new IOException("Failed to create temp directory " + generatedDir.getName()); + + return generatedDir; + } +} \ No newline at end of file diff --git a/src/main/jni/TestJNI.c b/src/main/jni/TestJNI.c new file mode 100644 index 00000000..a7ca4b05 --- /dev/null +++ b/src/main/jni/TestJNI.c @@ -0,0 +1,9 @@ +#include +#include +#include "TestJNI.h" + +JNIEXPORT void JNICALL Java_org_warp_picalculator_TestJNI_sayHello(JNIEnv *env, jobject thisObj) +{ + printf("Hello World!\n"); + return; +} diff --git a/src/main/jni/TestJNI.h b/src/main/jni/TestJNI.h new file mode 100644 index 00000000..65b039db --- /dev/null +++ b/src/main/jni/TestJNI.h @@ -0,0 +1,21 @@ +/* DO NOT EDIT THIS FILE - it is machine generated */ +#include +/* Header for class org_warp_picalculator_TestJNI */ + +#ifndef _Included_org_warp_picalculator_TestJNI +#define _Included_org_warp_picalculator_TestJNI +#ifdef __cplusplus +extern "C" { +#endif +/* + * Class: org_warp_picalculator_TestJNI + * Method: sayHello + * Signature: ()V + */ +JNIEXPORT void JNICALL Java_org_warp_picalculator_TestJNI_sayHello + (JNIEnv *, jobject); + +#ifdef __cplusplus +} +#endif +#endif diff --git a/src/main/jni/TestJNI.o b/src/main/jni/TestJNI.o new file mode 100644 index 00000000..956366cc Binary files /dev/null and b/src/main/jni/TestJNI.o differ diff --git a/src/main/jni/makefile b/src/main/jni/makefile new file mode 100644 index 00000000..947fb5ce --- /dev/null +++ b/src/main/jni/makefile @@ -0,0 +1,24 @@ +# Define a variable for classpath +CLASS_PATH = ../../../target/classes +PATH1A = org/warp/picalculator +PATH1B = org.warp.picalculator + +# Define a virtual path for .class in the bin directory +vpath %.class $(CLASS_PATH)/$(PATH1A) + +all : picalculatornative.dll + +# $@ matches the target, $< matches the first dependancy +picalculatornative.dll : TestJNI.o + x86_64-w64-mingw32-gcc.exe -m64 -mtune=generic -Wl,--add-stdcall-alias -shared -o ../resources/$@ $< + +# $@ matches the target, $< matches the first dependancy +TestJNI.o : TestJNI.c TestJNI.h + x86_64-w64-mingw32-gcc.exe -m64 -mtune=generic -I"G:\Program Files\Java\jdk1.8.0_144\include" -I"G:\Program Files\Java\jdk1.8.0_144\include\win32" -c $< -o $@ + +# $* matches the target filename without the extension +TestJNI.h : TestJNI.class + javah -o "$*.h" -classpath "$(CLASS_PATH)" $(PATH1B).$* + +clean : + rm TestJNI.h TestJNI.o ../resources/picalculatornative.dll \ No newline at end of file diff --git a/src/main/resources/picalculatornative.dll b/src/main/resources/picalculatornative.dll new file mode 100644 index 00000000..f3258dbb Binary files /dev/null and b/src/main/resources/picalculatornative.dll differ