Java Native Interface. C++. Linux. Первые шаги

в 13:49, , рубрики: c++, java, jni, linux, Песочница, метки: , , ,

На хабре уже были подобные статьи, но для Windows и «ничего не понятно» для новичков вроде меня. В принципе ничего сложного нет, но есть где споткнуться и на долго засесть в поисковиках, как было со мной.

Для чего и как применять C/C++ в приложении для Java каждый придумает самостоятельно, останавливаться на этом не буду, скажу только, что при работе с каким-либо оборудованием такая связка может быть действительно полезной.

Так же не буду касаться нюансов с типами данных, скажу лишь, что примитивные типы(такие как jint или jdouble) отличаются от родных для C++ ровно ничем.

И так. Для начала в двух словах о том как это работает. Мы пишем на C++ код, например, обрабатывающий некое изображение и возвращающий нам количество котят. Затем компилируем динамически загружаемую библиотеку и подгружаем её в нашем приложении на Java, которое скачивает нам картинку из VK. Не сложно.

Для вызова функций из подключённой библиотеки необходимо объявить соответствующие методы в каком-либо классе и пометить их как native. Далее по ним будет сгенерирован заголовочный файл содержащий прототипы функций с соответствующими сигнатурами.

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 получаем утилитой javah из скомпилированного class-файла.

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

Полученный заголовочный файл лучше вообще не трогать, т.к. он может изменяться при сборке проекта. Просто инклудим его в cpp файле и описываем функции там, главное ничего не напутать с именами функций и параметрами, лучше копировать или поручить это 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));
}

Собираем динамическую библиотеку.

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

Флаги -fpic -c -shared обязательны для корректной компиляции.

Необходимо что бы имя библиотеки соответствовало шаблону lib[name].so, те, кто хорошо знаком с Linux, скорее всего считают это очевидным, но здесь я зависал дольше всего, т.к. в существующих статьях для win32 ни слова про префикс lib.

Осталось написать класс на Java с методом main, скомпилировать его и запустить приложение.

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

При запуске указываем виртуальной машине путь к директории с динамической библиотекой, т.к. по умолчанию искать она будет только по путям записанным в переменных среды.

java -Djava.library.path="." AppClass

Для того что бы вручную не компилировать каждый файл отдельно можно написать простой Makefile, который в дальнейшем можно использовать с 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

Скачать весь код можно на GitHub

Автор: Framework

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js