├── .gitignore ├── src ├── main │ ├── java │ │ └── ie │ │ │ └── agisoft │ │ │ ├── App.java │ │ │ ├── Test.java │ │ │ └── LibraryLoader.java │ └── c++ │ │ ├── ie_agisoft_Test.cpp │ │ └── ie_agisoft_Test.h └── test │ └── java │ └── ie │ └── agisoft │ └── AppTest.java ├── Makefile ├── LICENSE ├── pom.xml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | *~ 3 | 4 | -------------------------------------------------------------------------------- /src/main/java/ie/agisoft/App.java: -------------------------------------------------------------------------------- 1 | package ie.agisoft; 2 | 3 | /** 4 | * Hello world! 5 | * 6 | */ 7 | public class App { 8 | 9 | public static void main(String[] args) { 10 | System.out.println("Hello World!"); 11 | new Test().hello(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/ie/agisoft/Test.java: -------------------------------------------------------------------------------- 1 | package ie.agisoft; 2 | 3 | class Test { 4 | 5 | public native void hello(); 6 | 7 | static { 8 | try { 9 | LibraryLoader.loadLibrary("agisoft"); 10 | } catch (Exception e) { 11 | System.err.println(e); 12 | System.exit(1); 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /src/main/c++/ie_agisoft_Test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "ie_agisoft_Test.h" 4 | 5 | JNIEXPORT void JNICALL Java_ie_agisoft_Test_hello(JNIEnv *, jobject) { 6 | printf("Hello World\n"); 7 | #ifdef __cplusplus 8 | printf("__cplusplus is defined\n"); 9 | #else 10 | printf("__cplusplus is NOT defined\n"); 11 | #endif 12 | return; 13 | } 14 | -------------------------------------------------------------------------------- /src/main/c++/ie_agisoft_Test.h: -------------------------------------------------------------------------------- 1 | /* DO NOT EDIT THIS FILE - it is machine generated */ 2 | #include 3 | /* Header for class ie_agisoft_Test */ 4 | 5 | #ifndef _Included_ie_agisoft_Test 6 | #define _Included_ie_agisoft_Test 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | /* 11 | * Class: ie_agisoft_Test 12 | * Method: hello 13 | * Signature: ()V 14 | */ 15 | JNIEXPORT void JNICALL Java_ie_agisoft_Test_hello 16 | (JNIEnv *, jobject); 17 | 18 | #ifdef __cplusplus 19 | } 20 | #endif 21 | #endif 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC=gcc 2 | CFLAGS=-c -Wall -fPIC -I/usr/lib/jvm/java-7-openjdk-amd64/include 3 | LDFLAGS=-fPIC -shared 4 | 5 | SOURCES_DIR=src/main/c++ 6 | OBJECTS_DIR=target/c++ 7 | EXECUTABLE=target/classes/libagisoft.so 8 | 9 | SOURCES=$(shell find '$(SOURCES_DIR)' -type f -name '*.cpp') 10 | OBJECTS=$(SOURCES:$(SOURCES_DIR)/%.cpp=$(OBJECTS_DIR)/%.o) 11 | 12 | all: $(EXECUTABLE) 13 | 14 | $(EXECUTABLE): $(OBJECTS) 15 | $(CC) $(LDFLAGS) $(OBJECTS) -o $@ 16 | 17 | $(OBJECTS): $(SOURCES) 18 | mkdir -p $(OBJECTS_DIR) 19 | $(CC) $(CFLAGS) $< -o $@ 20 | 21 | clean: 22 | rm -rf $(OBJECTS_DIR) $(EXECUTABLE) 23 | -------------------------------------------------------------------------------- /src/test/java/ie/agisoft/AppTest.java: -------------------------------------------------------------------------------- 1 | package ie.agisoft; 2 | 3 | import junit.framework.Test; 4 | import junit.framework.TestCase; 5 | import junit.framework.TestSuite; 6 | 7 | /** 8 | * Unit test for simple App. 9 | */ 10 | public class AppTest 11 | extends TestCase 12 | { 13 | /** 14 | * Create the test case 15 | * 16 | * @param testName name of the test case 17 | */ 18 | public AppTest( String testName ) 19 | { 20 | super( testName ); 21 | } 22 | 23 | /** 24 | * @return the suite of tests being tested 25 | */ 26 | public static Test suite() 27 | { 28 | return new TestSuite( AppTest.class ); 29 | } 30 | 31 | /** 32 | * Rigourous Test :-) 33 | */ 34 | public void testApp() 35 | { 36 | assertTrue( true ); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Greg Kubisa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/main/java/ie/agisoft/LibraryLoader.java: -------------------------------------------------------------------------------- 1 | package ie.agisoft; 2 | 3 | import java.io.*; 4 | 5 | /** 6 | * Contains helper methods for loading native libraries, particularly JNI. 7 | * 8 | * @author gkubisa 9 | */ 10 | public class LibraryLoader { 11 | 12 | /** 13 | * Loads a native shared library. It tries the standard System.loadLibrary 14 | * method first and if it fails, it looks for the library in the current 15 | * class path. It will handle libraries packed within jar files, too. 16 | * 17 | * @param name name of the library to load 18 | * @throws IOException if the library cannot be extracted from a jar file 19 | * into a temporary file 20 | */ 21 | public static void loadLibrary(String name) throws IOException { 22 | try { 23 | System.loadLibrary(name); 24 | } catch (UnsatisfiedLinkError e) { 25 | String filename = System.mapLibraryName(name); 26 | InputStream in = LibraryLoader.class.getClassLoader().getResourceAsStream(filename); 27 | int pos = filename.lastIndexOf('.'); 28 | File file = File.createTempFile(filename.substring(0, pos), filename.substring(pos)); 29 | file.deleteOnExit(); 30 | try { 31 | byte[] buf = new byte[4096]; 32 | OutputStream out = new FileOutputStream(file); 33 | try { 34 | while (in.available() > 0) { 35 | int len = in.read(buf); 36 | if (len >= 0) { 37 | out.write(buf, 0, len); 38 | } 39 | } 40 | } finally { 41 | out.close(); 42 | } 43 | } finally { 44 | in.close(); 45 | } 46 | System.load(file.getAbsolutePath()); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 4.0.0 7 | ie.agisoft 8 | jni-maven 9 | jar 10 | 1.0-SNAPSHOT 11 | jni-maven 12 | http://maven.apache.org 13 | 14 | UTF-8 15 | 16 | 17 | 18 | 19 | junit 20 | junit 21 | 3.8.1 22 | test 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-antrun-plugin 30 | 1.7 31 | 32 | 33 | process-classes 34 | 35 | 38 | Generating JNI headers 39 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | run 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jni-maven 2 | ========= 3 | 4 | jni-maven is a sample project intended to be used as a template for Java applications using JNI. 5 | 6 | Requirements 7 | ------------ 8 | 9 | You'll need the following: 10 | - JDK 11 | - gcc 12 | - maven 13 | - ant 14 | - make 15 | 16 | Usage 17 | ----- 18 | 19 | ```bash 20 | git clone https://github.com/gkubisa/jni-maven.git jni-maven 21 | cd jni-maven 22 | mvn package 23 | java -cp target/jni-maven-1.0-SNAPSHOT.jar ie.agisoft.App 24 | ``` 25 | 26 | How it works 27 | ------------ 28 | 29 | [maven-antrun-plugin][] is added to the *process-classes* phase of the build. It is used to: 30 | 31 | - generate the C++ headers using *javah*. They are created in src/main/c++. 32 | I did not use *javah* ant target, because it assumes Sun JDK, while I'm using OpenJDK. 33 | - run *make* to build the native code 34 | 35 | The Makefile compiles the native code from src/main/c++ into \*.o files which are stored in target/c++. 36 | Then it runs the linker which produces a single \*.so object stored in *target/classes*. 37 | 38 | Because the \*.so file is in *target/classes*, it's included in the final JAR file. This way the 39 | whole project produces a single artifact and can be easily used as a dependency by other Maven projects. 40 | 41 | The final bit is the custom module loader *LibraryLoader.loadLibrary*, which extracts the 42 | native library from the jar into a temporary file and loads it - the standard *System.loadLibrary* cannot do it. 43 | 44 | Limitations / possible improvements 45 | ----------------------------------- 46 | 47 | 1. Native code build currently works only on Linux 48 | 2. Only C++ is supported as the native language 49 | 3. The paths in the Makefile are hard-coded. It would be nice to initialize them from Maven properties. 50 | 51 | **Patches welcome!** 52 | 53 | Credits 54 | ------- 55 | 56 | 1. - *jni-maven* is a simplified 57 | implementation of the concept presented in this article 58 | 2. - *LibraryLoader* class in based on the code from this 59 | project (*V8ScriptEngineFactory.java*) 60 | 61 | [maven-antrun-plugin]: http://maven.apache.org/plugins/maven-antrun-plugin/ 62 | --------------------------------------------------------------------------------