Java Nativa Interface(+example for Android)
Несмотря на довольно долгую историю Виртуальной машины Ява(Java) и победоносное наступление языка Kotlin, язык С до сих пор в 2021 году остается 8 в мире по популярности, а С++ 5 -ый. В мире огромное количество кода – библиотек и приложений, написанных на С/C+. Переписывание этого кода может занять не одну тысячу человеко-часов. Именно для этого и был придуман – мост JNI(Java Native Interface). Смысл его предельно прост – создать простой интерфейс для встраивания кода, вызова функций и даже запуска виртуальной машины Ява из кода на С/C++. К огромному облегчению – в среде Android Studio процесс интеграции и написания смешанного кода предельно прост(по моим наблюдениям в IDE – Eclipse это все же проще, чем в Netbeans).
Возьмем гипотетический пример(который впрочем частично реализован) – использование приложения qmail для отправки и приема почты. Для начала импортируем весь код в папку cpp. Теперь необходимо изменить конфигурационный файл CMmakLists.txt
include(tools.cmake) project("exampleproject") set(qmail-1.03_srcs sendmail.c ) PREPEND(qmail-1.03_srcs_with_path "qmail-1.03" ${qmail-1.03_srcs}) add_library(qmail-1.03 SHARED ${qmail-1.03_srcs_with_path}) add_library( exampleproject SHARED native-lib.cpp) find_library( log-lib log) target_link_libraries( ${log-lib})
Я привык пользоваться директивой PREPEND, не забудьте про ее объявление в доп файле сборки tools.cmake
FUNCTION(PREPEND var prefix) SET(listVar "") FOREACH(f ${ARGN}) LIST(APPEND listVar "${prefix}/${f}") ENDFOREACH(f) SET(${var} "${listVar}" PARENT_SCOPE) ENDFUNCTION(PREPEND)
По сути на этом вся “магия” заканчивается. Далее в Ява коде объявляем нативную(собственную) библиотеку и определяем нативный метод, который мы будем вызывать. Определение нативного метода похоже на объявление метода в интерфейсе – только заголовок без тела:
private void sendEmailAcrossQmail(){ sendMail(); } /** * A native method that is implemented by the 'exampleproject' native library, * which is packaged with this application. */ public native void sendMail(); static { System.loadLibrary("exampleproject"); }
Теперь мы подходим к самому ответственному моменту – файлу native-lib.cpp.
#include <jni.h> #include <string> #include "qmail-1.03/qmail-smtpd.h" extern "C" JNIEXPORT void JNICALL Java_online_salattime_exampleproject_MainActivity_sendMail( JNIEnv* env, jobject /* this */) { setup(); }
Java_online_salattime_exampleproject_MainActivity_sendMail – очень внимательно необходимо следить при редактировании путей. Здесь указан пакет, класс вызова и метод. Также необходимо рассмотреть 2 важные тему – это генерация исключений и вызов Ява методов из C/C++ кода. Отладка JNI кода весьма непростая задача. Если Вы уже знакомы с языком С, то знаете заведомо возникающие проблемы при использовании malloc(), указателей и необходимости высвобождения ресурсов.
Генерация исключений:
JNIEXPORT void JNICALL Java_online_salattime_exampleproject_MainActivity_sendMailWithThrow(JNIEnv* env, jclass cl, jobject out){ (*env)-> ThrowNew(env, (*env) -> FindClass(env,"java/lang/NullPointerException"),"TestThrow in own void"); return; }
Это тестовый пример и мы опустили много кода. Главное после вызова исключения не забыть вызвать операцию возврата – return, иначе выполнение метода продолжится.
Вызов Java методов из С/С++ кода
Самый простой пример – зачем это надо. Это реализация собственного класса логирования. Чтобы не писать лишний код в конфиг файле gradle для указания директив логирования в C/C++ коде, мы будем вызывать методы логирования в нашем придуманном классе Logg.
Для вызова ява методов необходимо:
- Получение класса из неявного параметра
- Получение идентификатора метода
- Вызов метода.
Сейчас мы на практике покажем, что все не так уж и сложно:
jsting strTag; jsting strMessage; //получаем класс class_Logg = (*env) -> GetObjectClass(env, out); //получаем метод id_i = (*env) - > GetMethodId(env, class_Logg,"i","(Ljava/lang/String;Ljava/lang/String;)V)" //вызываем метода (*env) -> CallVoidMethod(env, out,id_i, strTag,strMessage)
id_i – это id нашего метода с именем i(аналог класса Log).
Непонятная строка Ljava/lang/String – это кодирование сигнатуры. L- означает класс. V – возвращаемый тип void в нашем случае.
Типов сигнатур немного. Это:
B byte
C char
D double
F float
I int
J long
Lимя_класса; тип класса
S short
V void
Z boolean
На этом наш краткий экскурс в JNI закончен. Если у Вас возникли вопросы или Вы хотите, чтобы мы рассмотрели более детально эту тему, задавайте вопросы, пишите комментарии.