На хабре уже были подобные статьи, но для Windows и «ничего не понятно» для новичков вроде меня. В принципе ничего сложного нет, но есть где споткнуться и на долго засесть в поисковиках, как было со мной.
Для чего и как применять C/C++ в приложении для Java каждый придумает самостоятельно, останавливаться на этом не буду, скажу только, что при работе с каким-либо оборудованием такая связка может быть действительно полезной.
Так же не буду касаться нюансов с типами данных, скажу лишь, что примитивные типы(такие как jint или jdouble) отличаются от родных для C++ ровно ничем.
И так. Для начала в двух словах о том как это работает. Мы пишем на C++ код, например, обрабатывающий некое изображение и возвращающий нам количество котят. Затем компилируем динамически загружаемую библиотеку и подгружаем её в нашем приложении на Java, которое скачивает нам картинку из VK. Не сложно.
Для вызова функций из подключённой библиотеки необходимо объявить соответствующие методы в каком-либо классе и пометить их как native. Далее по ним будет сгенерирован заголовочный файл содержащий прототипы функций с соответствующими сигнатурами.
Header получаем утилитой javah из скомпилированного class-файла.
Полученный заголовочный файл лучше вообще не трогать, т.к. он может изменяться при сборке проекта. Просто инклудим его в cpp файле и описываем функции там, главное ничего не напутать с именами функций и параметрами, лучше копировать или поручить это IDE.
Собираем динамическую библиотеку.
Флаги -fpic -c -shared обязательны для корректной компиляции.
Необходимо что бы имя библиотеки соответствовало шаблону lib[name].so, те, кто хорошо знаком с Linux, скорее всего считают это очевидным, но здесь я зависал дольше всего, т.к. в существующих статьях для win32 ни слова про префикс lib.
Осталось написать класс на Java с методом main, скомпилировать его и запустить приложение.
При запуске указываем виртуальной машине путь к директории с динамической библиотекой, т.к. по умолчанию искать она будет только по путям записанным в переменных среды.
Для того что бы вручную не компилировать каждый файл отдельно можно написать простой Makefile, который в дальнейшем можно использовать с Eclipse
Скачать весь код можно на GitHub
Для чего и как применять 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
