📜 ⬆️ ⬇️

Java Native Interface. C ++. Linux. The first steps

On Habré there were already similar articles, but for Windows and “nothing is clear” for newbies like me. In principle, there is nothing difficult, but there is where to stumble and sit for a long time in search engines, as was the case with me.

For what and how to use C / C ++ in a Java application, everyone will come up with their own, I will not dwell on this, I will only say that when working with any equipment such a bundle can be really useful.

Also, I will not touch on the nuances with data types, I will only say that primitive types (such as jint or jdouble) differ from C ++ native types by nothing.

So. First, a few words about how it works. We write C ++ code, for example, processing a certain image and returning the number of kittens. Then we compile the dynamically loaded library and load it in our Java application, which downloads us a picture from VK. Not difficult.
')
To call functions from a connected library, you must declare the corresponding methods in a class and mark them as native. Further on, a header file containing prototypes of functions with corresponding signatures will be generated.

NativeCode.java
public class NativeCode { //            loadLibrary //        NativeCode static { System.loadLibrary( "nativecode" ); } public NativeCode() { //   srand    srand(); } //      public native int getInt(); //     int public native void showInt(int i); //     int public native void showIntArray(int[] array); //    public native int getRandomInt(); //       public native void addOneToArray(int[] array); private native void srand(); } 



Header is obtained by the javah utility from the compiled class file.

 javac NativeCode.java javah -jni -o NativeCode.h NativeCode 


NativeCode.h
 /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class by_framework_nativeapp_NativeCode */ #ifndef _Included_by_framework_nativeapp_NativeCode #define _Included_by_framework_nativeapp_NativeCode #ifdef __cplusplus extern "C" { #endif /* * Class: by_framework_nativeapp_NativeCode * Method: getInt * Signature: ()I */ JNIEXPORT jint JNICALL Java_by_framework_nativeapp_NativeCode_getInt (JNIEnv *, jobject); /* * Class: by_framework_nativeapp_NativeCode * Method: showInt * Signature: (I)V */ JNIEXPORT void JNICALL Java_by_framework_nativeapp_NativeCode_showInt (JNIEnv *, jobject, jint); /* * Class: by_framework_nativeapp_NativeCode * Method: showIntArray * Signature: ([I)V */ JNIEXPORT void JNICALL Java_by_framework_nativeapp_NativeCode_showIntArray (JNIEnv *, jobject, jintArray); /* * Class: by_framework_nativeapp_NativeCode * Method: getRandomInt * Signature: ()I */ JNIEXPORT jint JNICALL Java_by_framework_nativeapp_NativeCode_getRandomInt (JNIEnv *, jobject); /* * Class: by_framework_nativeapp_NativeCode * Method: addOneToArray * Signature: ([I)V */ JNIEXPORT void JNICALL Java_by_framework_nativeapp_NativeCode_addOneToArray (JNIEnv *, jobject, jintArray); /* * Class: by_framework_nativeapp_NativeCode * Method: srand * Signature: ()V */ JNIEXPORT void JNICALL Java_by_framework_nativeapp_NativeCode_srand (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif 



The resulting header file is better not to touch at all, because It may change when building a project. Just enable it in the cpp file and describe the functions there, the main thing is not to confuse anything with the names of functions and parameters, it is better to copy or assign this to the IDE.

NativeCode.cpp
 #include <iostream> #include <ctime> #include <cstdlib> #include <iomanip> #include "NativeCode.h" JNIEXPORT jint JNICALL Java_by_framework_nativeapp_NativeCode_getInt (JNIEnv *enc, jobject obj) { int input = 1; std::cout<<"Input number: "; std::cin>>input; if(input<0) input = 0; return input; } JNIEXPORT void JNICALL Java_by_framework_nativeapp_NativeCode_showInt (JNIEnv *env, jobject obj, jint i) { std::cout<<"Output number: "<<i<<std::endl; } JNIEXPORT void JNICALL Java_by_framework_nativeapp_NativeCode_showIntArray (JNIEnv *env, jobject obj, jintArray jarray) { int len = env->GetArrayLength(jarray); std::cout<<"Array length: "<<len<<std::endl; jint* arr = env->GetIntArrayElements(jarray, 0); for(int i = 0; i < len; i++) { std::cout<<std::setw(5)<<i<<": "<<std::setw(4)<<arr[i]<<std::endl; } env->ReleaseIntArrayElements(jarray, arr, 0); } JNIEXPORT jint JNICALL Java_by_framework_nativeapp_NativeCode_getRandomInt (JNIEnv *env, jobject obj) { int i = rand()%100; return i; } JNIEXPORT void JNICALL Java_by_framework_nativeapp_NativeCode_addOneToArray (JNIEnv *env, jobject obj, jintArray jarray) { int len = env->GetArrayLength(jarray); jint* arr = env->GetIntArrayElements(jarray, 0); for(int i = 0; i < len; i++) { ++(*(arr+i)); } // .. GetIntArrayElements    ,  //    Java   env->ReleaseIntArrayElements(jarray, arr, 0); } JNIEXPORT void JNICALL Java_by_framework_nativeapp_NativeCode_srand (JNIEnv *env, jobject obj) { srand(time(NULL)); } 



We collect dynamic library.
 g++ -o libnativecode.o -I"/usr/lib/jvm/java-1.7.0-openjdk-amd64/include" -I"/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/linux" -fpic -c NativeCode.cpp g++ -o libnativecode.so -shared libnativecode.o 


The -fpic -c -shared flags are required for correct compilation.

It is necessary that the library name matches the lib [name] .so pattern, those who are familiar with Linux most likely consider this obvious, but here I have been hanging out the longest because In existing articles for win32, not a word about the lib prefix.

It remains to write a Java class with the main method, compile it and run the application.

AppClass.java
 public class AppClass { public static void main(String[] args) { //    NativeCode     //     NativeCode nc = new NativeCode(); int i = nc.getInt(); nc.showInt(++i); int[] array = new int[i]; //     for(int j = 0; j < i; j++) { array[j] = nc.getRandomInt(); } nc.showIntArray(array); nc.addOneToArray(array); nc.showIntArray(array); } } 



 javac AppClass.java 


When starting, we specify the virtual machine path to the directory with the dynamic library, since by default, it will only search for paths written in environment variables.
 java -Djava.library.path="." AppClass 


In order not to manually compile each file separately, you can write a simple Makefile, which you can later use with Eclipse

Makefile
 all : NativeCode.so NativeCode.so : NativeCode.obj g++ -o bin/libnativecode.so -shared bin/libnativecode.o NativeCode.obj: cpp_src/NativeCode.cpp java_headers g++ -o bin/libnativecode.o -I"/usr/lib/jvm/java-1.7.0-openjdk-amd64/include" -I"/usr/lib/jvm/java-1.7.0-openjdk-amd64/include/linux" -fpic -c cpp_src/NativeCode.cpp java_headers: java_class_files javah -jni -o cpp_src/NativeCode.h -classpath bin by.framework.nativeapp.NativeCode java_class_files: src/by/framework/nativeapp/NativeCode.java src/by/framework/nativeapp/AppClass.java mkdir -p bin javac -d bin -cp bin src/by/framework/nativeapp/NativeCode.java javac -d bin -cp bin src/by/framework/nativeapp/AppClass.java 



You can download the entire code on GitHub.

Source: https://habr.com/ru/post/160293/


All Articles