Всем привет! Меня зовут Роман Аймалетдинов, я разрабатываю клиентское приложение Ситимобил. Продолжаю свою серию статей по JNI, так как технология используется редко, но иногда она бывает очень полезной (или просто интересной). В этот раз я покажу примеры решений на JNI, которые совсем немного сложнее, чем hello world. И если вы не знакомы с JNI, то советую начать с первой части.

Содержание
JNI-типы.
Returnв нативном методе.Как передать
List<List>.Как пройтись по циклу в нативе.
Вызов Java-метода из С++.
JNI-типы
В JNI мы вынуждены использовать специальные типы для native-окружения. Мы не можем просто так передать int и работать с ним в С++, хотя это и кажется логичным. Таким образом, типы существующие в Java для JNI, дублируются с префиксом j. Например:
boolean → jboolean
byte → jbyte
char → jchar
И дело не ограничивается одними лишь примитивными типами, как описано в документации Oracle. Вам также придётся иметь дело с некоторыми неудобствами, то есть трансформациями.

Как с этим работать — рассмотрим в следующей главе.
Return в нативном методе
В файле AwesomeLib, который содержит наши нативные методы, создадим метод
getRandom, который возвращаетint.
public class AwesomeLib { static { System.loadLibrary("nativeLib"); } public native void helloHabr(); public native int getRandom(); // <- new method }
Затем генерируем заголовок командой
javac -h . AwesomeLib.java, наш файл.hобновится и появится новый метод:
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class nativelib_AwesomeLib */ #ifndef _Included_nativelib_AwesomeLib #define _Included_nativelib_AwesomeLib #ifdef __cplusplus extern "C" { #endif /* Class: nativelib_AwesomeLib Method: helloHabr Signature: ()V */ JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_helloHabr( JNIEnv *, jobject ); /* (Наш новый метод!) Class: nativelib_AwesomeLib Method: getRandom Signature: ()I */ JNIEXPORT jint JNICALL Java_nativelib_AwesomeLib_getRandom( JNIEnv *, jobject ); #ifdef __cplusplus } #endif #endif
Теперь напишем код на С++, который будет возвращать случайное число. Подключим необходимую для этого библиотеку
#include <ctime>, затем реализуем простейшее решение получения числа в С++:
#include "nativelib_AwesomeLib.h" #include <iostream> #include <ctime> // new lib JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_helloHabr( JNIEnv* env, jobject thisObject ) { std::cout << "Hello Habr! This is C++ code!!" << std::endl; } // Новый метод JNIEXPORT jint JNICALL Java_nativelib_AwesomeLib_getRandom( JNIEnv* env, jobject obj ) { std::srand(std::time(nullptr)); int randomValue = std::rand(); return randomValue; }
Как видно из примера (JNIEXPORT jint), возвращаем не int, а jint, хотя и в этом примере всё будет работать отлично, но в некоторых случаях придется делать каст к JNI-формату. Например, так: return (jint) variable;.
Как передать List
Как передать в конструктор простой тип? Проблем нет. А что делать, если нужно передать что-то посложнее? Например, List<List<Float>>?
Создаем ещё один метод в нашей крутой библиотеке:
public class AwesomeLib { // code public native void printMatrix(float[][] matrix); }
Но в заголовке (
nativelib_AwesomeLib.h) будетjobjectArray— совсем не то, что мы хотели бы увидеть. Но придётся с этим жить.
/* * Class: nativelib_AwesomeLib * Method: printMatrix * Signature: ([[F)V */ JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_printMatrix( JNIEnv *, jobject, jobjectArray );
Пишем реализацию на С++:
JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_printMatrix( JNIEnv * env, jobject obj, jobjectArray matrix ) { std::cout << "C++ code: print jobjectArray: " << matrix << std::endl; }
Передали, попробуем запустить программу (не забывайте, что после каждого изменения
.cppнеобходимо запустить в консоли команды для обновления.dll, это описано в первой статье):

Видим только адрес, а если будет matrix[0] или matrix[0][0] ? На самом деле, ни то, ни другое просто не скомпилируется, хотя будет нормально работать для jintArray. Всё дело в jobjectArray: сначала нужно получить из массива необходимые типы, скастить и только потом печатать. Так мы подошли к следующей теме.
Пройтись циклом по массиву в native
Чтобы пройтись по циклу, нужно:
Узнать длину массива
GetArrayLength.Получить элемент массива объектов
GetObjectArrayElement.Скастить полученный
jobjectArrayв необходимый намjfloatArray.Написать типичный код на С++.
JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_printMatrix( JNIEnv * env, jobject obj, jobjectArray matrix ) { std::cout << "C++ code: print jobjectArray:" << std::endl; int sizeFirstArr = env->GetArrayLength(matrix); for (int i = 0; i < sizeFirstArr; i++) { jfloatArray secondArr = (jfloatArray) env->GetObjectArrayElement(matrix, i); jfloat *elements = env->GetFloatArrayElements(secondArr, 0); int sizeSecondArr = env->GetArrayLength(secondArr); for (int k = 0; k < sizeSecondArr; k++) { float value = elements[k]; std::cout << value << ", "; } std::cout << std::endl; } }
Запустим наш код и посмотрим, что получилось:

Классно, напечаталось. Но знаете, что ещё? Это же C++, никаких тебе тут gc и магии, будь добр, подчищай за собой.
env->ReleaseFloatArrayElements(secondArr, elements, 0); env->DeleteLocalRef(secondArr);
Необходимо высвобождать память самостоятельно. Пусть этот код вызывается и из Java, но тут нет сборщика мусора, и утечки — ваша головная боль. Справедливости ради, считаю, что сложным кодом на C++ должна заниматься другая команда, которая его хорошо знает, но если это ваш pet-project и вы один, то не забывайте и об особенностях.
Как вызвать Java-метод из C++?
Мы научились простым, но основным операциям. Как вызвать метод, как вернуть значение и передать в конструктор. Но иногда хочется из С++ дёрнуть нечто полезное из Java. Как провернуть такой трюк?
В нашем классе
AwesomeLib.javaсоздадим обычный метод, который вызовем из C++. Чтобы было интереснее, передадим в него float и int. Вызовем изMainнаш метод из первой статьи —helloHabr. Немного его модифицируем и вызовем из него Java-метод. Таким образом у нас получится последовательность вызовов:Java → C++ → Java.
public class AwesomeLib { // from article: JNI Part 1 public native void helloHabr(); // new code public void printNativeResult(float value1, int value2) { System.out.println( "Java code: value1: " + value1 + " value2: " + value2 ); } }
Идем в
AwesomeLib.cppи изменяем метод из первой статьи:
JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_helloHabr( JNIEnv* env, jobject thisObject ) { std::cout << "Hello Habr! This is C++ code!!" << std::endl; jclass cls_awesome_lib = env -> GetObjectClass(thisObject); jmethodID mid_compare = env->GetMethodID( cls_awesome_lib, "printNativeResult", "(FI)V" ); // call method env->CallVoidMethod( thisObject, mid_compare, 2.0, 3 ); }
Что тут происходит?
Сначала находим и получаем Java-объект, чтобы потом вызывать его методы.
Получаем id метода, чтобы обратиться к нему. Мы указываем название метода и то, что в нашем случае он содержит в конструкторе
“(FI)”, поскольку у нас в нём float и int.Vговорит о том, что это void-метод, а не возвращаемый.Вызываем метод.
Обновляем .dll и запускаем наш код:

Ура, мы умеем запускать код Java из native-кода!
Чуть подробнее про GetMethodID:
“**(FI)V**”→ void someFunc(float, int)“**(FF)V**”→ void someFunc(float, float)“**(FI)Z**”→ boolean someFunc(float, int)“**()Z**”→ boolean someFunc()
Обращаемся к информации с сайта Oracle:

Заключение
Напоследок скажу, что JNI — это не совсем обычный C/C++. Но не стоит бояться, нужно исследовать! В моей небольшой серии про JNI осталась последняя статья, в ней я расскажу про самое важное и интересное: производительность! Напишу простые синтетические тесты и покажу, когда в JNI есть смысл, а когда нет.
