Java, попри деякі «недоліки», є потужною і, головне, в більшості випадків, самодостатньою мовою програмування. Під самодостатністю я розумію можливість написання програм, що вирішують якусь конкретну задачу без залучення інших мов програмування.
Однак я не дарма написав, що самодостатність мови Java проявляється саме в більшості випадків. Іноді без залучення допоміжних засобів написати програму повністю неможливо. Наприклад, необхідно скористатися кодом, який забезпечує низькорівневий доступ до «заліза» того комп'ютера, на якому виконується програма. У мові програмування Java, яка за своєю ідеологією є кросплатформовою, засоби низькорівневого доступу до апаратної частини непередбачені.
Для того, щоб вирішити цю проблему, розробники Java включили в мову можливість звернення з Java-програм до програм, реалізованих на інших мовах програмування через native-методи. Підсистема Java, що реалізує цю можливість, називається JNI (Java Native Interface – інтерфейс мови Java, що дозволяє звернення до native-методів).
Не так давно мені потрібно було працювати з однією бібліотекою на Java. Проблема була в тому, що мені потрібно було використовувати методи під GPU (пишіть в коментарях, чи хотіли б декілька статей на тему CUDA), проте в Java реалізації цієї бібліотеки такої можливості не було і мені довелося викликати методи з C програми. В цій статті поговоримо про практичне застосування JNI.
Створення класу
Для початку нам потрібно створити якийсь клас, що буде містити нативний метод.
public class HelloJNI {
static {
System.loadLibrary("hello"); // Завантаження бібліотеки hello.dll
}
// Оголошення нативного методу sayHello(), котрий не приймає аргументів і повертає void
private native void sayHello();
public static void main(String[] args) {
new HelloJNI().sayHello(); // виклик нативного методу
}
}
Ми оголосили клас HelloJNI
, що містить нативний метод sayHello
. Давайте трохи розберемося з кодом.
Блок static
означає те, що наша бібліотека буде завантажена під час завантаження класу. Для того, щоб програма могла знайти бібліотеку – потрібно додати її шлях у classpath
. Це можна зробити під час запуску програми додавши аргумент -Djava.library.path=PATH_LIB
. Також є й інший варіант: замість методу loadLibrary
використовувати load
. Але в такому випадку потрібно повністю вказати шлях до бібліотеки (включаючи розширення dll
або so
).
У нас є клас, але він ще ніяк не поєднаний з нашою бібліотекою (просто у нас її ще немає).
Створення бібліотеки
Наступним кроком буде компіляція файлу та створення h
файлу.
javac HelloJNI.java
javah HelloJNI
Після чого ми отримаємо наступний файл заголовку:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class HelloJNI */
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: HelloJNI
* Method: sayHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Що ми можемо дізнатися, поглянувши на цей файл? По-перше, те, що до коду програми на С буде включений файл jni.h
, в якому, до речі, містяться всі необхідні для роботи JNI функції. По-друге, ми побачимо, що той метод, який в класі HelloJNI був описаний як
private native void sayHello();
в програмі на C описаний трохи по-іншому, а саме:
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *, jobject);
Як бачимо, функція приймає два аргументи, хоча, ми їх не вказували. Що це таке?
-
JNIEnv*
– вказівник на JNI середовище, яке дає вам доступ до всіх функцій JNI; -
jobject
– вказівник на об'єктthis
у Java.
Визначення JNIEXPORT
в файлі jni_md.h
, який викликається з jni.h
, описано наступним чином:
#define JNIEXPORT __declspec(dllexport)
У тому ж файлі визначення JNICALL
описано так:
#define JNICALL __stdcall
Після цього стає зрозумілим, що всі ці «страшні» описи є просто позначеннями, використовуваними при виклику звичайної функції, що експортується.
Імплементація на C
Тепер нам потрібно реалізувати описану функцію у c
файлі.
#include <jni.h>
#include <stdio.h>
#include "HelloJNI.h"
JNIEXPORT void JNICALL Java_HelloJNI_sayHello(JNIEnv *env, jobject thisObj) {
printf("Hello World!\
");
return;
}
Як бачимо, функція виводить у консоль рядок та повертає void
. Тут головне не забути під'єднати файл заголовку, який ми створили раніше. Файл jni.h
знаходиться у каталогах JAVA_HOME\\include
та JAVA_HOME\\include\\win32
.
Нарешті, ми можемо скомпілювати файл:
gcc -Wl --add-stdcall-alias -I"%JAVA_HOME%\\include" -I"%JAVA_HOME%\\include\\win32" -shared -o hello.dll HelloJNI.c
Поясню декілька параметрів:
-
-Wl --add-stdcall-alias
– опція, що запобігає виникненню помилки лінкувальника (UnsatisfiedLinkError); -
-I
– додаткові бібліотеки для включення (в нашому випадку, для включення файлу jni.h); -
-shared
– для генерації динамічної бібліотеки; -
-o
– задає ім'я вихідного файлу.
Тепер ми можемо запустити Java програму:
java HelloJNI
Hello World!
Пам'ятайте, якщо ви використовували метод loadLibrary
для завантаження бібліотеки, то запускати програму потрібно так:
java -Djava.library.path=PATH_TO_LIB HelloJNI
Hello World!
Висновок
В статті я описав загальні поняття, як можна використовувати JNI. За допомогою цієї технології також можна викликати методи класу, створювати нові об'єкти, передавати різні параметри (масиви, рядки тощо), повертати різні значення і багато іншого. Детальніше можна прочитати тут і тут.
Використання в мові Java native-методів порушує принцип кросплатформеності мови Java. Програма, що використовує DLL, стає сильно залежною від платформи, на якій реалізована DLL. Використання native-методів можна використовувати в тих випадках, коли передбачається використання основної програми (Java) на різних платформах, в той час як програми у вигляді native-методів планується розробити для кожної конкретної платформи. Якщо програму на Java, що використовує native-методи, планується застосовувати тільки на тій платформі, на якій реалізовані native-методи, то навіщо взагалі та кросплатформеність?
Ще одним недоліком є те, що з нативного методу можна отримати доступ до будь-якої частини системи, що суперечить методології Java (однією із вимог до Java є вимога безпеки).
Проте, попри недоліки – програміст сам обирає, які технології йому використовувати.
Ще немає коментарів