Взаємодія Java та C/C++

6 хв. читання

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 є вимога безпеки).

Проте, попри недоліки – програміст сам обирає, які технології йому використовувати.

Помітили помилку? Повідомте автору, для цього достатньо виділити текст з помилкою та натиснути Ctrl+Enter
Codeguida 1.7K
Приєднався: 1 рік тому
Коментарі (0)

    Ще немає коментарів

Щоб залишити коментар необхідно авторизуватися.

Вхід