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 закончен. Если у Вас возникли вопросы или Вы хотите, чтобы мы рассмотрели более детально эту тему, задавайте вопросы, пишите комментарии.