Java Native Interface C ++. Linux First steps

On a habr already there were similar articles, but for Windows and "it is not clear" for beginners like me. In principle, there is nothing complicated, but there is where to stumble and sit in search engines for a long time, as it was 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 can only say that when working with any equipment, such a combination can be really useful.

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

So. To begin with in a nutshell about how it works. We write C ++ code, for example, that processes a certain image and returns the number of kittens to us. Then we compile the dynamically loaded library and load it in our Java application, which downloads the picture from VK to us. Not difficult.

To call functions from the connected library, you must declare the appropriate methods in a class and mark them as native. Next, a header file containing function prototypes 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 
/* 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



It is better not to touch the resulting header file at all, because It may change during the assembly of the project. Just include it in a cpp file and describe the functions there, the main thing is not to mess up with the function names and parameters, it is better to copy or instruct this IDE.

NativeCode.cpp
#include 
#include 
#include 
#include 
#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: "<GetArrayLength(jarray);
	std::cout<<"Array length: "<GetIntArrayElements(jarray, 0);
	for(int i = 0; i < len; i++) {
		std::cout<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 are building a 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 proper compilation.

It is necessary that the library name matches the template lib [name] .so , those who are familiar with Linux, most likely consider this obvious, but here I hung for the longest time, because in existing articles for win32 not a word about the lib prefix.

It remains to write a class in Java 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


At startup, we indicate to the virtual machine the path to the directory with the dynamic library, because By default, it will search only by the paths recorded in the environment variables.
java -Djava.library.path="." AppClass


In order not to manually compile each file separately, you can write a simple Makefile, which can later be used 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 all the code on GitHub

Also popular now: