Java Nativa Interface(

Java Nativa Interface(+example for Android). Собственные методы Java с С/C++ с примерами кода для Android

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 Anc C code

Java_online_salattime_exampleproject_MainActivity_sendMail – очень внимательно необходимо следить при редактировании путей. Здесь указан пакет, класс вызова и метод.  Также необходимо рассмотреть 2 важные тему – это генерация исключений и вызов Ява методов из C/C++  кода. Отладка JNI кода весьма непростая задача. Если Вы уже знакомы с языком С, то знаете заведомо возникающие проблемы при использовании malloc(), указателей и необходимости высвобождения ресурсов.

Java Nativa Interface(

Генерация исключений:

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.

Для вызова ява методов необходимо:

  1. Получение класса из неявного параметра
  2. Получение идентификатора метода
  3. Вызов метода.

Сейчас мы на практике покажем, что все не так уж и сложно:

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

 

Leave a Reply

Please disable your adblocker or whitelist this site!