Використання JNI

Взаємодія кодів JAVA і С / С + + може здійснюватися двома способами:
С / С + +-код отримує управління безпосередньо з JAVA-програми шляхом виклику
власного (NATIVE) методу; С / С + +-код динамічно завантажує JVM за допомогою
INVOCATION API. У другому випадку, по суті, реалізується спеціалізована JVM,
так як розробник С / С + +-коду сам вирішує, в якій послідовності виконувати
JAVA-код (коли і які JAVA-об'єкти створювати, які методи викликати і т.
д.).


Розглянемо першу з зазначених можливостей.


Для того щоб передати керування З / С + +-коду з JAVA-програми, необхідно
створити власний JAVA-метод, згенерувати за допомогою утиліти JAVAH
заголовний файл для С / С + +-функцій, розробити самі функції, в які буде
передаватися управління, і відтранслювати їх, помістивши в бібліотечний файл.
Після створення бібліотеки її можна завантажувати з JAVA-програми для подальшого
виклику власних методів.


Створення власного JAVA-методу


Власний метод створюється шляхом додавання до його опису специфікатора
NATIVE, при цьому він не повинен мати реалізації (так само як і методи в описі
інтерфейсу). Специфікатор NATIVE повідомляє компілятору, що реалізація даного
методу буде представлена у вигляді відкомпільованого С / С + +-коду, який міститься у
бібліотечний файл. Коли JVM зустрічає звернення до власного методу,
відбувається виклик відповідної С / С + +-функції. Крім опису собственнного
методу, JAVA-код повинен динамічно завантажити бібліотеку, яка містить
С / С + +-функцію з реалізацією даного методу. Для цього в класі JAVA.LANG.SYSTEM
існує метод PUBLIC STATIC VOID LOADLIBRARY (STRING LIBNAME), що завантажує
зазначену бібліотеку. Наступний приклад демонструє опис власного
методу.

CLASS SYSTEMSPECIFIC {
STATIC
{
SYSTEM.LOADLIBRARY(“SYSSPEC”);
}
NATIVE VOID
DOSPECIFIC();
}

У наведеному прикладі метод DOSPECIFIC()
є власним, і його С / С + +-реалізація знаходиться в бібліотеці SYSSPEC.
Метод LOADLIBRARY() викликається в
статичному ініціалізатор, що забезпечує єдиний виклик цього методу
після завантаження класу SYSTEMSPECIFIC
завантажувачем класів (CLASS LOADER). У
принципі, LOADLIBRARY() можна викликати
більше одного разу (наприклад, в конструкторі), однак завантаження бібліотеки буде
відбуватися тільки при першому зверненні до LOADLIBRARY(), Оскільки при подальших викликах
цього методу визначається, що бібліотека вже завантажена і буде просто
повертатися управління.


Метод LOADLIBRARY() перетворює свій
параметр відповідно до того, як називаються бібліотечні файли на конкретній
платформі. У даному прикладі SYSSPEC
перетвориться в SYSSPEC.DLL і LIBSYSSPEC.SO для WIN32 і UNIX відповідно.
Метод LOADLIBRARY() використовує стандартний
алгоритм пошуку бібліотеки для даної платформи. Для WIN32 DLL повинна знаходитися
або в поточному каталозі процесу, або в каталозі, що містить EXE-файл, тобто
виконуваний модуль JVM, що знаходиться в підкаталозі BIN основного каталогу JAVA,
або в системному каталозі WIN32, або каталозі WINDOWS або в каталогах,
зазначених у змінній оточення PATH. Для
UNIX бібліотечний файл повинен знаходитися або в поточному каталозі процесу, або
в підкаталозі LIB основного каталогу JAVA, або в каталогах, перерахованих в
змінної оточення LD_LIBRARY_PATH. Якщо
зазначену бібліотеку знайти не вдається, метод LOADLIBRARY() генерує виняткову ситуацію
JAVA.LANG.UNSATISFIEDLINKERROR. Однак
дана ситуація виникає не тільки в цьому випадку. Коли інтерпретатор зустрічає
виклик власного методу, він шукає його (точніше його повну сигнатуру) у списку
методів завантажених бібліотек. Якщо метод не знайдено, то генерується зазначена
виняткова ситуація.


Для більш надійної роботи з власними методами можна використовувати, до
Наприклад, наступний код:

PUBLIC CLASS APP {
PUBLIC
STATIC VOID MAIN(STRING ARGS) {
SYSTEMSPECIFIC SS = NEW
SYSTEMSPECIFIC();
TRY {
SS.DOSPECIFIC();
}
CATCH
(UNSATISFIEDLINKERROR E) {
SYSTEM.OUT.PRINTLN ("метод не знайдений (" + E +
“)”);
}
}
}
CLASS SYSTEMSPECIFIC {
STATIC {
TRY
{
SYSTEM.LOADLIBRARY(“SYSSPEC”);
}
CATCH (UNSATISFIEDLINKERROR E)
{
SYSTEM.OUT.PRINTLN ("бібліотека не знайдена (" + E +
“)”);
}
}
NATIVE VOID DOSPECIFIC();
}

Компіляція програм, що містять власні методи, нічим не відрізняється від
компіляції звичайних програм. Наприклад, якщо записати попередній приклад у файл з
ім'ям APP.JAVA, то для його компіляції необхідно виконати наступну
команду:

C: JAVAC APP.JAVA

Створення заголовки


Створення С / С + +-коду необхідно починати зі створення заголовного файлу. Його
можна написати вручну або скористатися утилітою JAVAH. Другий шлях
краще, тому що допускає меншу кількість помилок. При зверненні до
утиліті JAVAH вказується ім'я класу і параметр-JNI. Без нього JAVAH буде
генерувати файл у форматі JDK 1.0 NI. Ім'я класу представляє собою повне
кваліфіковане ім'я класу. Наприклад:

JAVAH -JNI
JAVA.LANG.RUNTIME

Перед використанням утиліти JAVAH відповідний JAVA-клас повинен бути
скомпільований в CLASS-файл. Утиліта JAVAH аналізує CLASS-файл і будує
заголовний файл, в якому перераховані оголошення С / С + +-функцій,
представляють реалізації відповідних власних методів. В якості імен
створюваних заголовних файлів використовуються повні кваліфіковані імена
класів, які описані у вказаному файлі і містять власні методи.
Наприклад, якщо виконати наступні команди:

JAVAC
APP.JAVA
JAVAH -JNI SYSTEMSPECIFIC

то JAVAH згенерує наступний файл SYSTEMSPECIFIC.H:

/* DO NOT EDIT THIS FILE – IT IS MACHINE
GENERATED */
#INCLUDE <JNI.H>
/* HEADER
FOR CLASS SYSTEMSPECIFIC */
#IFNDEF
_INCLUDED_SYSTEMSPECIFIC
#DEFINE _INCLUDED_SYSTEMSPECIFIC
#IFDEF _
_CPLUSPLUS
EXTERN “C” {
#ENDIF
/*
* CLASS:
SYSTEMSPECIFIC
* METHOD: DOSPECIFIC
* SIGNATURE:
()V
*/
JNIEXPORT VOID JNICALL JAVA_SYSTEMSPECIFIC_DOSPECIFIC(JNIENV
*, JOBJECT);
#IFDEF _ _CPLUSPLUS
}
#ENDIF
#ENDIF

Як вказувалося вище, даний файл можна створити вручну або за допомогою утиліти
JAVAH. В останньому випадку не рекомендується вносити в нього які-небудь зміни,
тому що при подальшому застосуванні JAVAH до даного класу всі внесені
зміни будуть втрачені.


Директива препроцесора #INCLUDE <JNI.H>
включає файл JNI.H (з підкаталогу INLCUDE основного каталогу
JAVA), в якому знаходяться всі необхідні оголошення типів і функцій для
реалізації власного методу.


Макроси JNIEXPORT і JNICALL необхідні тільки для платформи WIN32, де вони
розкриваються відповідно в __DECLSPEC (DLLEXPORT) і __STDCALL і дозволяють
більш ефективно будувати DLL. Платформа UNIX використовує для цих цілей звичайні
З-угоди, тому зазначені макроси розкриваються у порожні рядки.


Як видно з прикладу, ім'я С / С + +-функції значно відрізняється від імені
власного JAVA-методу. Важливим поняттям при побудові імені С / С + +-функції і
використанні JNI-функцій є сигнатура методу (SIGNATURE або METHOD
ARGUMENTS SIGNATURE).


Сигнатура методу


Сигнатура методу – це скорочена форма запису параметрів методу і типів
, що повертається. Слід підкреслити, що в сигнатуру не входять ні ім'я
методу, ні імена параметрів. JNI формує сигнатури відповідно до правил,
представленими в табл. 1.


Таблиця 1
























































Знак сигнатури JAVA-тип
Z BOOLEAN
B BYTE
C CHAR
S SHORT
Internet INT
J LONG
F FLOAT
V VOID
D DOUBLE
L повне кваліфіковане ім'я класу повне кваліфіковане ім'я класу
[Тип тип []
(Типи аргументів) повертається тип повна сигнатура методу


Проілюструємо ці правила на прикладах:



Повна інформація про правила освіти сигнатури методу представлена в
файлі SIGNATURE.H.


Правила формування імені С / С + +-функції


Ім'я С / С + +-функції формується шляхом послідовного з'єднання наступних
компонентів:



Використання імен з сигнатурою на кінці необхідно тільки у випадку
перевантаження двох або більше власних методів (перевантаження із звичайним методом не
важна, так как обычные методы не будут находиться в создаваемой библиотеке, что,
однак, не допускає наявності власного і звичайного методу з однаковими
іменами і сигнатурами).


Для відповідності лексіграфіческім правилами С / С + + і використання
UNICODE-кодування, застосовуються додаткові правила перетворення,
представлені в табл. 2.


Таблиця 2



















Вихідний символ Результуюча послідовність
“_” _1
“;” _2
“[“ _3
символ UNICODE з кодом ХХХХ _0ХХХХ

Нижче наведено приклад JAVA-класу з власними методами:

PACKAGE TESTPACKAGE;
ABSTRACT CLASS TEST {
PUBLIC NATIVE
VOID M1(STRING[] SA, OBJECT O, INT[][] IA2);
PUBLIC NATIVE FLOAT[] M1(DOUBLE
D, TEST T);
PUBLIC NATIVE TEST M3(INT I);
}

і відповідні їм імена С / С + +-функцій:


JNIEXPORT VOID JNICALL
JAVA_TESTPACKAGE_TEST_M1___3LJAVA_LANG_STRING_2LJAVA_LANG_OBJECT_2_3_3I
(JNIENV
*, JOBJECT, JOBJECTARRAY, JOBJECT, JOBJECTARRAY);
JNIEXPORT JFLOATARRAY
JNICALL
JAVA_TESTPACKAGE_TEST_M1__LJAVA_LANG_DOUBLE_2LTESTPACKAGE_TEST_2
(JNIENV *,
JOBJECT, JOBJECT, JOBJECT);
JNIEXPORT JOBJECT JNICALL
JAVA_TESTPACKAGE_TEST_M3
(JNIENV *, JOBJECT, JINT);


Розглянемо типи параметрів, які отримує на вході С / С + +-функція при її
виклику.


Типи і структури даних JNI


JNI використовує цілий набір типів для своїх функцій і для формальних
параметрів С / С + +-функцій, представляють реалізацію власних методів. Всі ці
типи описані у файлі JNI.H, який включається в будь-заголовний файл для
JNI. Файл JNI.H використовує стандартну техніку препроцессірованія з макросом
_CPLUSPLUS. Тим самим, в залежності від того, який (С + + або С) код
компілюється, будуть створюватися дві трохи відрізняються версії опису типів.
Кожна з них вимагає певного синтаксису доступу.


Файл JNI_MD.H містить системно-залежні опису JINT, JLONG і JBYTE. У
цьому ж файлі визначені макроси JNIEXPORT і JNICALL. Тип VOID використовується без
перевизначення.


Слід зазначити, що для представлення строкових об'єктів JNI використовує
скорочений варіант формату UTF-8.


Першим аргументом С / С + +-функції, що представляє реалізацію власного
методу, є вказівник на структуру JNIENV. Сенс цього покажчика визначає
важливу ідею, що лежить в основі реалізації JNI. Якщо відволіктися від конкретної
мови, то покажчик на JNIENV, або інтерфейсний покажчик (JNI INTERFACE
POINTER), є покажчиком на масив покажчиків, кожен з яких вказує
на прикладну функцію JNI. Тільки через цей покажчик С / С + +-функція може
отримати доступ до функцій і ресурсів JNI. У разі С + + (макрос _CPLUSPLUS
визначено) тип JNIENV є структурою, а в разі З – покажчиком на
структуру. У силу цього, для доступу до функцій JNI в С і С + + застосовується
різний синтаксис:

//
C
JNIEXPORT VOID JNICALL JAVA_SYSTEMSPECIFIC_DOSPECIFIC (JNIENV * ENV,
JOBJECT THIS) {
JINT VERSION = (*ENV)->GETVERSION(ENV);
Е
}
// C++
JNIEXPORT VOID JNICALL
JAVA_SYSTEMSPECIFIC_DOSPECIFIC(JNIENV* ENV, JOBJECT THIS) {
JINT VERSION =
ENV->GETVERSION();
Е
}

Головною перевагою такої організації функцій JNI є легкість
модифікації і подальшого розширення інтерфейсу.


Покажчик на JNIENV дійсний тільки в поточному потоці (THREAD). JVM
гарантує передачу одного і того ж інтерфейсного покажчика всім методам,
викликуваним з даного потоку. Тим самим заборонено передавати інтерфейсний
покажчик іншому потоку. Якщо методи викликаються з різних потоків, то в цьому
випадку кожен метод отримує різні інтерфейсні покажчики.


Якщо С / С + +-функція являє реалізацію нестатичні власного
методу, то другим параметром функції є об'єкт типу JOBJECT. Даний
параметр є посиланням на JAVA-об'єкт, для якого був викликаний
відповідний власний метод. Якщо функція представляє статичний
власний метод, то другим параметром є об'єкт типу JCLASS,
визначає JAVA-клас, для якого викликаний власний метод класу (CLASS
METHOD).


Наступні параметри С / С + +-функції відповідають параметрам власного
методу (якщо власний метод їх не містить, то реалізує його С / С + +-функція
має тільки два згадані вище параметра).


JNI функції


JNI визначає 210 прикладних функцій. Доступ до них з С / С + +-функції можна
отримати через інтерфейсний покажчик JNIENV *, який передається кожній
С / С + +-функції, які представляють реалізацію власного методу. Всі функції
розділені на 14 груп:



функцій необхідно тільки в тому випадку, якщо С / С + +-функція
здійснює будь-яка взаємодія з JVM: виклик JAVA-методів, доступ до
даними, створення JAVA-об'єктів і т.д.


Нижче наведено приклад JAVA-програми, яка виводить на друк кількість
вільної пам'яті на диску С для платформи WIN32. Для цього використовується
власний метод і відповідна реалізаційна С / С + +-функція, що викликає при
своїй роботі функцію WIN32 API.

// Файл
APP.JAVA
PUBLIC CLASS APP {
PUBLIC STATIC VOID MAIN(STRING ARGS[])
{
SYSTEMSPECIFIC SS = NEW SYSTEMSPECIFIC();
TRY {
LONG BYTES =
SS.GETCDRIVEFREESPACE();
IF (BYTES != -1) {
LONG KB = BYTES / 1024;

SYSTEM.OUT.PRINTLN ("на диску C: вільно" + KB + "KB");
}
ELSE
{
SYSTEM.OUT.PRINTLN ("сталася помилка в С / С + +-функції");
}
}
CATCH
(UNSATISFIEDLINKERROR E) {
SYSTEM.OUT.PRINTLN ("метод не знайдений (" + E +
“)”);
}
}
}
CLASS SYSTEMSPECIFIC {
STATIC {
TRY
{
SYSTEM.LOADLIBRARY(“SYSSPEC”);
}
CATCH (UNSATISFIEDLINKERROR E)
{
SYSTEM.OUT.PRINTLN ("бібліотека не знайдена (" + E +
“)”);
}
}
NATIVE LONG GETCDRIVEFREESPACE();
}
/ / Файл
SYSTEMSPECIFIC.CPP
#INCLUDE “SYSTEMSPECIFIC.H”
#INCLUDE
<WINDOWS.H>
JNIEXPORT JLONG JNICALL
JAVA_SYSTEMSPECIFIC_GETCDRIVEFREESPACE (JNIENV *, JOBJECT) {
DWORD
SCTRPERCLSTR, BYTESPERSCTR, FREECLSTR, CLSTR;
BOOL RES =
GETDISKFREESPACE ("C:", & SCTRPERCLSTR, & BYTESPERSCTR, & FREECLSTR,
&CLSTR);
RETURN RES == TRUE ? SCTRPERCLSTR * BYTESPERSCTR * FREECLSTR :
-1;
}

Для успішної компіляції коду JAVA і С / С + + на платформі WIN32 необхідно
правильно встановити змінні оточення PATH, LIB, INCLUDE і CLASSPATH (багато
з цих значень можна задати як параметри відповідних компіляторів).


Якщо записати вихідні тексти попереднього прикладу у файли APP.JAVA і
SYSTEMSPECIFIC.CPP відповідно, то для їх компіляції необхідно виконати
наступні команди (передбачається, що вихідні файли знаходяться в каталозі
C:TESTNATIVE):

C:TESTNATIVE> JAVAC
APP.JAVA
C:TESTNATIVE> JAVAH -JNI SYSTEMSPECIFIC
C:TESTNATIVE>
CL -W3 SYSTEMSPECIFIC.CPP -FESYSSPEC.DLL -TP -LD -MD -LINK
JAVAI.LIB

Для запуску програми необхідно виконати: C:TESTNATIVE> JAVA APP
на диску С: вільно 324567
KB
C:TESTNATIVE>

Для трансляції С / С + +-файлів можна використовувати будь-який компілятор, що допускає
створення 32-бітових DLL.


Використання INVOCATION API


Використання INVOCATION API дозволяє вбудовувати JVM в додатки без
необхідності їх статичного зв'язування з кодом самої JVM. Нагадаємо, що в цьому
випадку управління спочатку знаходиться в С / С + +-програмі. INVOCATION API складається
з невеликого набору функцій, які дозволяють створювати і знищувати JVM в поточному
процесі, приєднувати і від'єднувати поточний потік від JVM (інтерфейсний
покажчик існує тільки в рамках даного потоку).


У загальному випадку, для вбудовування JVM у програму її необхідно створити і
проініціалізувати, приєднати, якщо це необхідно, до якого-небудь потоку, а
після закінчення роботи з JVM видалити її з пам'яті процесу. Після того як JVM
створена і отримано інтерфейсний покажчик, можна використовувати будь-які
JNI-функції.


Розглянемо приклад С + +-коду, який створює JVM в процесі своєї роботи і
викликає статичний метод JAVA-класу. Нижче наведено вихідні тексти
JAVA-класу і C + +-коду:

// Файл
INVOCATIONAP.JAVAI
PUBLIC CLASS INVOCATIONAPI {
STATIC VOID TEST()
{
SYSTEM.OUT.PRINTLN(“HELLO FROM JAVA CODE”);
}
}
/ / Файл
INVOCATIONAPI.CPP
#INCLUDE <JNI.H>
VOID MAIN() {
JAVAVM*
JVM;
JNIENV* ENV;
/ / Ініціалізація
JDK1_1INITARGS
VMARGS;
JNI_GETDEFAULTJAVAVMINITARGS(&VMARGS);
VMARGS.CLASSPATH =
“C:/JDK1.1/LIB/CLASSES.ZIP;C:/TEST/NATIVE”;
/ / Створення
JVM
JNI_CREATEJAVAVM(&JVM, &ENV, &VMARGS);
/ / Отримання посилання
на клас INVOCATIONAPI
JCLASS CLS = ENV->FINDCLASS(“INVOCATIONAPI”);
//
виклик статичного методу TEST
JMETHODID MID = ENV->GETSTATICMETHODID(CLS,
“TEST”, “()V”);
ENV->CALLSTATICVOIDMETHOD(CLS, MID);
/ / Видалення
JVM
JVM->DESTROYJAVAVM();

Для компіляції наведеної програми на платформі WIN32 необхідно виконати
наступні команди (передбачається, що змінні оточення PATH, LIB, INCLUDE і
CLASSPATH встановлені вірно і вихідні файли знаходяться в каталозі
C:TESTNATIVE):

C:TESTNATIVE JAVAC
INVOCATIONAPI.JAVA
C:TESTNATIVE CL -W3 -NOLOGO INVOCATIONAPI.CPP
-FEINVOCATIONAPI -TP -MD -LINK
-NOLOGO JAVAI.LIB

А для запуску програми:

C:TESTNATIVE
INVOCATIONAPI
HELLO FROM JAVA CODE
C:TESTNATIVE

На перший погляд, наявність JNI і повна публікація його специфікацій може
залучити розробників до написання нестерпного JAVA-коду, що, в свою
чергу, може значно знизити ефективність застосування технології JAVA.
Однак насправді JNI сприяє зворотному процесу.


Практично для будь-якої програми можна абсолютно точно визначити
необхідність у багатоплатформного виконанні. Для систем, які не потребують
в багатоплатформності, JNI надає інфраструктуру, за допомогою якої
JAVA-додаток може взаємодіяти з операційною системою й апаратурою, в
середовищі яких воно виконується. Таким чином, JNI є природним
доповненням JAVA-технології. Він дозволяє використовувати її як для створення
переносите (клієнтських) додатків, так і для створення високопродуктивних
(Серверних) систем, що використовують всю специфіку конкретної платформи і
апаратури, зберігаючи в той же час головне достоїнство JAVA – сучасний
об'єктно-орієнтований підхід.


Автор: Микита Іванов
www.javaportal.ru

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*