JNI Часть 2: Примеры

от автора

Всем привет! Меня зовут Роман Аймалетдинов, я разрабатываю клиентское приложение Ситимобил. Продолжаю свою серию статей по 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 в нативном методе

  1. В файле AwesomeLib, который содержит наши нативные методы, создадим метод getRandom, который возвращает int.

public class AwesomeLib {        static {         System.loadLibrary("nativeLib");     }      public native void helloHabr();      public native int getRandom(); // <- new method }
  1. Затем генерируем заголовок командой 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 
  1. Теперь напишем код на С++, который будет возвращать случайное число. Подключим необходимую для этого библиотеку #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>>?

  1. Создаем ещё один метод в нашей крутой библиотеке:

public class AwesomeLib {     // code       public native void printMatrix(float[][] matrix); }
  1. Но в заголовке (nativelib_AwesomeLib.h) будет jobjectArray — совсем не то, что мы хотели бы увидеть. Но придётся с этим жить.

/*  * Class:     nativelib_AwesomeLib  * Method:    printMatrix  * Signature: ([[F)V  */ JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_printMatrix(     JNIEnv *,     jobject,     jobjectArray ); 
  1. Пишем реализацию на С++:

 JNIEXPORT void JNICALL Java_nativelib_AwesomeLib_printMatrix(     JNIEnv * env,     jobject obj,     jobjectArray matrix ) {     std::cout << "C++ code: print jobjectArray: " << matrix << std::endl; } 
  1. Передали, попробуем запустить программу (не забывайте, что после каждого изменения .cpp необходимо запустить в консоли команды для обновления .dll, это описано в первой статье):

Видим только адрес, а если будет matrix[0] или matrix[0][0] ? На самом деле, ни то, ни другое просто не скомпилируется, хотя будет нормально работать для jintArray. Всё дело в jobjectArray: сначала нужно получить из массива необходимые типы, скастить и только потом печатать. Так мы подошли к следующей теме.

Пройтись циклом по массиву в native

Чтобы пройтись по циклу, нужно:

  1. Узнать длину массива GetArrayLength.

  2. Получить элемент массива объектов GetObjectArrayElement.

  3. Скастить полученный jobjectArray в необходимый нам jfloatArray.

  4. Написать типичный код на С++.

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. Как провернуть такой трюк?

  1. В нашем классе 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         );     } } 
  1. Идем в 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-метод, а не возвращаемый.

  • Вызываем метод.

  1. Обновляем .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 есть смысл, а когда нет.


ссылка на оригинал статьи https://habr.com/ru/company/citymobil/blog/650915/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *