Java Native Interface (JNI) Что такое JNI ? Java Native Interface (JNI) — стандартный механизм Java, с помощью которого Java-код может взаимодействовать с системным или прикладным собственным кодом (вызывать его и вызываться им), написанным на языках подобных С, C++ или ассемблера, скомпилированным и работающим непосредственно под управлением той или иной операционной системы. Что такое JNI ? Взаимодействие Java с другими языками Взаимодействие кодов JAVA и С/С++ может осуществляться двумя способами С/С++ - код получает управление непосредственно из JAVA-программы путем вызова собственного (NATIVE) метода С/С++ - код динамически загружает JVM с помощью INVOCATION API JNI – NATIVE метод Необходимо создать собственный JAVA-метод Сгенерировать с помощью утилиты JAVAH заголовочный файл для С/С++ - функций Разработать сами функции, в которые будет передаваться управление Оттранслировать функции поместив в библиотечный файл После создания библиотеки ее можно загружать из JAVAпрограммы для последующего вызова собственных методов. JNI – INVOCATION API метод Использование INVOCATION API позволяет встраивать JVM в приложения (без необходимости их статического связывания с кодом самой JVM) управление изначально находится в С/С++ - программе. INVOCATION API состоит из небольшого набора функций, позволяющих создавать и уничтожать JVM в текущем процессе присоединять и отсоединять текущий поток от JVM Используя JNI, собственные методы могут Создавать, проверять и обновлять Java объекты (включая массивы и типы String) Вызывать Java методы Ловить и выбрасывать исключения Загружать классы и получать информацию о классах Выполнять проверку типов во время исполнения JNI определяет ~210 прикладных функций Доступ к JNI-функциям из С/С++ -функции можно получить через интерфейсный указатель JNIENV* Указатель JNIENV* передается каждой С/С++ функции, представляющей реализацию собственного метода Все ~210 функции разделены на ~14 групп информация о версии JNI операции с классами исключения (EXCEPTIONS) обработка глобальных и локальных ссылок операции с объектами доступ к данным объекта вызов методов объекта (INSTANCE METHOD) доступ к статическим данным объекта вызов методов класса (CLASS METHOD) операции со строковыми объектами операции с массивами регистрация собственных методов операции с мониторами (MONITOR OPERATIONS) интерфейс с JVM JNI и исключения в Java Throw( ) Выбрасывает существующий объект исключения. Используется в собственном объекте для повторного выбрасывания исключения. ThrowNew( ) ExceptionOccurred( ) ExceptionDescribe( ) Создает новый объект исключения и выбрасывает его. Определяет, было ли исключение уже выброшено, но еще не очищено. Печатает исключение и содержимое стека. ExceptionClear( ) Очищает рассматриваемое исключение. FatalError( ) Вызывает фатальную ошибку. Возврата нет. Основные этапы реализации native метода используя JNI 1. Создание класса который объявляет native метод. 2. Компиляция программы 3. Используя javah –jni создать заголовочный файл C/С++ (.h) 4. Написание реализации выполнения native метода, (.с) 5. Компиляция С кода в dll или so библиотеку 6. Запуск Java приложения используя полученную библиотеку Создание собственного JAVA-метода Собственный метод создается путем добавления к его описанию спецификатора NATIVE, при этом он не должен иметь реализации (так же как и методы в описании интерфейса). Спецификатор NATIVE сообщает компилятору, что реализация данного метода будет представлена в виде откомпилированного С/С++ - кода, помещенного в библиотечный файл. Когда JVM встречает обращение к собственному методу, происходит вызов соответствующей С/С++ - функции. Помимо описания собственного метода, JAVA-код должен динамически загрузить библиотеку, содержащую С/С++ - функцию с реализацией данного метода. Создание класса который объявляет native метод class HelloWorld { //native метод public native void displayHello(); //Статическая инициализация static { //динамически загрузить библиотеки System.loadLibrary("c_library"); } } public static void main(String[] args) { //создание нового объекта HelloWorld hw = new HelloWorld(); // вызов метода реализованного в С/С++ hw.displayHello(); } Метод LOADLIBRARY() вызывается в статическом инициализаторе, что обеспечивает единственный вызов этого метода после загрузки класса. можно вызывать более одного раза (например, в конструкторе), загрузка библиотеки будет происходить только при первом обращении к LOADLIBRARY(), при последующих вызовах этого метода определяется, что библиотека уже загружена и будет просто возвращаться управление. преобразует свой параметр в соответствии с тем, как именуются библиотечные файлы на конкретной платформе (dll, so) использует стандартный алгоритм поиска библиотеки для данной платформы LOADLIBRARY() алгоритм поиска библиотеки для WIN32 DLL Для WIN32 DLL может находиться • в текущем каталоге процесса • в каталоге, содержащем EXE-файл (то есть исполняемый модуль JVM, находящийся в подкаталоге BIN основного каталога JAVA) • в системном каталоге WIN32 • в каталоге WINDOWS • в каталогах, указанных в переменной окружения PATH LOADLIBRARY() алгоритм поиска библиотеки для UNIX Для UNIX библиотечный файл может находиться • в текущем каталоге процесса • в подкаталоге LIB основного каталога JAVA • в каталогах, перечисленных в переменной окружения LD_LIBRARY_PATH LOADLIBRARY() исключительная ситуация JAVA.LANG.UNSATISFIEDLINKERROR Если указанную библиотеку найти не удается Если метод не найден Компиляция программы Ничем не отличается от компиляции обычных программ. Например, если записать пример в файл с именем APP.JAVA, то для его компиляции необходимо выполнить следующую команду: C:\ JAVAC APP.JAVA Создание заголовочного файла Его можно написать: • В ручную • Воспользоваться утилитой JAVAH Утилита JAVAH При обращении к утилите JAVAH указывается имя класса и параметр -JNI Утилита JAVAH анализирует CLASS-файл и строит заголовочный файл В качестве имен создаваемых заголовочных файлов используются полные квалифицированные имена классов, которые описаны в указанном файле и содержат собственные методы Создание заголовочного файла используя JAVAH JAVAH -JNI HelloWorld Выходной файл HelloWorld.h Содержимое заголовочного файла #include <JNI.H> /* Header for class Example */ #ifndef _Included_Example #define _Included_Example #ifdef __cplusplus extern "C" { #endif /* * Class: Example * Method: nativeMethod * Signature: ()V */ JNIEXPORT VOID JNICALL Java_displayHello_helloWorld (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif Содержимое заголовочного файла Директива препроцессора #INCLUDE <JNI.H> включает файл JNI.H (из подкаталога INLCUDE основного каталога JAVA), в котором находятся все необходимые объявления типов и функций для реализации собственного метода. Макросы JNIEXPORT и JNICALL необходимы только для платформы WIN32, где они раскрываются соответственно в __DECLSPEC(DLLEXPORT) и __STDCALL и позволяют более эффективно строить DLL. Платформа UNIX использует для этих целей обычные С-соглашения, поэтому указанные макросы раскрываются в пустые строки. имя С/С++ - функции значительно отличается от имени собственного JAVA-метода. При построении имени С/С++ - функции и использовании JNI-функций является сигнатура метода (SIGNATURE или METHOD ARGUMENTS SIGNATURE). Сигнатура метода Сигнатура метода - это сокращенная форма записи параметров метода и типов возвращаемого значения. В сигнатуру не входят ни имя метода, ни имена параметров. JNI формирует сигнатуры в соответствии с правилами METHOD ARGUMENTS SIGNATURE Знак сигнатуры JAVA-тип Z BOOLEAN B BYTE C CHAR S SHORT I INT J LONG F FLOAT V VOID D DOUBLE L полное квалифицированное имя класса полное квалифицированное имя класса [ тип тип[] (типы аргументов) возвращаемый тип полная сигнатура метода SIGNATURE - примеры метод LONG M1(INT N, STRING S, INT[] ARR); • сигнатура (ILJAVA/LANG/STRING;[I)J; метод VOID M2(FLOAT N, BYTE[][] ARR, RUNTIME R); • сигнатура (F[[BLJAVA/LANG/RUNTIME;)V. Метод void funcC() • сигнатура ()V Правила формирования имени С/С++ - функции Формируется путем последовательного соединения следующих компонентов: • • • • • префикс JAVA_ полное квалифицированное имя класса; символ подчеркивания ("_") имя метода для перегружаемых (OVERLOADED) методов - два символа подчеркивания ("_ _") с последующей сигнатурой метода. Реализации выполнения native метода #include … #include “HelloWorld.h“ JNIEXPORT VOID JNICALL Java_displayHello_helloWorld (JNIEnv * je, jobject jo) { printf( "!!! In the native method !!!\n" ); return; } Типы и структуры данных JNI Все типы описаны в файле JNI.H JNI.H использует стандартную технику препроцессирования с макросом _CPLUSPLUS В зависимости от того, какой (С++ или С) код компилируется, будут создаваться две немного отличающиеся версии описания типов. Каждая из них требует определенного синтаксиса доступа. Доступа к функциям JNI Стиль С • JNIEXPORT VOID JNICALL JAVA_SYSTEMSPECIFIC_DOSPECIFIC(JNIENV* ENV, JOBJECT THIS) { JINT VERSION = (*ENV)->GETVERSION(ENV); Е } Стиль С++ • JNIEXPORT VOID JNICALL JAVA_SYSTEMSPECIFIC_DOSPECIFIC(JNIENV* ENV, JOBJECT THIS) { JINT VERSION = ENV->GETVERSION(); Е } Продолжение следует… Development Tool for Java-COM Bridge ( RJCB in JVI)