На хабре уже были подобные статьи, но для Windows и «ничего не понятно» для новичков вроде меня. В принципе ничего сложного нет, но есть где споткнуться и на долго засесть в поисковиках, как было со мной.
Для чего и как применять C/C++ в приложении для Java каждый придумает самостоятельно, останавливаться на этом не буду, скажу только, что при работе с каким-либо оборудованием такая связка может быть действительно полезной.
Так же не буду касаться нюансов с типами данных, скажу лишь, что примитивные типы(такие как jint или jdouble) отличаются от родных для C++ ровно ничем.
И так. Для начала в двух словах о том как это работает. Мы пишем на C++ код, например, обрабатывающий некое изображение и возвращающий нам количество котят. Затем компилируем динамически загружаемую библиотеку и подгружаем её в нашем приложении на Java, которое скачивает нам картинку из VK. Не сложно.
Для вызова функций из подключённой библиотеки необходимо объявить соответствующие методы в каком-либо классе и пометить их как native. Далее по ним будет сгенерирован заголовочный файл содержащий прототипы функций с соответствующими сигнатурами.
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
/* 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.
#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, скомпилировать его и запустить приложение.
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
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