Для прикладного програмування засобів Java у переважній більшості випадків вистачає. Однак іноді виникає необхідність підключити до програми ряд системних викликів. Або забезпечити доступ до бібліотек, написаним на інших мовах програмування. Для таких цілей в Java використовуються методи, оголошені з модифікатором native - "рідний". Це слово означає, що при виконанні методу виробляється виклик "рідного" для конкретної платформи двійкового коду, а не платформо-незалежного байта-коду як у всіх інших випадках. Заголовок "рідного" методу описується в класі Java, а його реалізація здійснюється на якому-небудь із мов програмування, що дозволяють створювати бібліотеки, що підключаються динамічно (DLL - Dynamic Link Library під Windows, Shared Objects під UNIX-Образними операційними системами).
Правило для оголошення й реалізації таких методів зветься JNI - Java Native Interface.
Оголошення "рідного" методу в Java має вигляд
Модифікатори native Повертаємий_типім.’я_методу(список параметрів);
Тіло «рідного» методу не задається - воно є зовнішнім і завантажується на згадку комп'ютера за допомогою завантаження тої бібліотеки, з якої цей метод повинен викликатися:
System.loadLibrary(«Ім’я_бібліотеки»);
При цьому ім'я бібліотеки задається без шляху й без розширення. Наприклад, якщо під Windows бібліотека має ім'я myLib.dll, або під UNIX або Linux має ім'я myLib.so, треба вказувати System.loadLibrary(«myLib») ;
У випадку, якщо файлу не знайдено, збуджується непроверяемая виняткова ситуація UnsatisfiedLinkError.
Якщо потрібно вказати ім'я бібліотеки зі шляхом, застосовується виклик
System.load («Ім’я_бібліотеки_з_шляхом»);
Який у всім іншому абсолютно аналогічний виклику loadLibrary.
Після того, як бібліотека завантажена, з погляду використання в програмі виклик "рідного" методу нічим не відрізняється від виклику будь-якого іншого методу.
Для створення бібліотеки з методами, призначеними для роботи в якості "рідних", звичайно використовується мова С++. В JDK існує утиліта javah.exe, призначена для створення заголовків C++ зі скомпільованих класів Java. Покажемо, як їй користуватися, на прикладі класу ClassWithNativeMethod. Задамо його в пакеті нашого додатка:
package java_example_pkg;
public class ClassWithNativeMethod {
/** Creates a new instance of ClassWithNativeMethod */
public ClassWithNativeMethod() {
}
public native void myNativeMethod();
}
Для того, щоб скористатися утилітою javah, скомпілюємо проект і перейдемо в папку build\classes. У ній будуть розташовуватися папка з пакетом нашого додатка java_example_pkg і папка META-INF. У режимі командного рядка виконаємо команду
javah.exe java_example_pkg.ClassWithNativeMethod
Задавати ім'я класу необхідно з повною кваліфікацією, тобто із вказівкою перед ним ім'я пакета. У результаті в папці з'явиться файл java_example_pkg_ClassWithNativeMethod.h з наступним умістом:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class java_example_pkg_ClassWithNativeMethod */
#ifndef _Included_java_example_pkg_ClassWithNativeMethod
#define _Included_java_example_pkg_ClassWithNativeMethod
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: java_example_pkg_ClassWithNativeMethod
* Method: myNativeMethod
* Signature: ()V
*/
JNIEXPORT void JNICALL
Java_java_1example_1pkg_ClassWithNativeMethod_myNativeMethod
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Функція Java_java_1example_1pkg_ClassWithNativeMethod_myNativeMethod(JNIEnv *, jobject), написана на C++, повинна забезпечувати реалізацію методу myNativeMethod() у класі Java. Ім'я функції C++ складається з: префікса Java, роздільника "_", модифікованого ім'я пакета (знаки підкреслення "_" заміняються на "_1"), роздільника "_", ім'я класу, роздільника "_", ім'я "рідного" методу. Перший параметр JNIEnv * у функції C++ забезпечує доступ "рідного" коду до параметрів і об'єктів, що передається з функції C++ в Java. Зокрема, для доступу до стеку. Другий параметр, jobject, - посилання на екземпляр класу, у якому заданий "рідний" метод, для методів об'єкта, і jclass - посилання на сам клас - для методів класу. У мові C++ немає посилань, але в Java всі змінні об'єктного типу є посиланнями. Відповідно, другий параметр ототожнюється із цієї змінної.
У реалізації методу потрібно оголосити змінні. Наприклад, якщо ми будемо обчислювати квадрат переданого в метод значення й повертати як результат значення параметра, зведене у квадрат (приклад чисто навчальний), код реалізації функції на C++ буде виглядати так:
#include "java_example_pkg_ClassWithNativeMethod.h"
JNIEXPORT jint JNICALL
Java_java_1example_1pkg_ClassWithNativeMethod_myNativeMethod
(JNIEnv *env, jobject obj, jint i ){
return i*i
};
Відзначимо, що при роботі з рядками й масивами для одержання й передачі параметрів потрібно використовувати змінну env. Наприклад, одержання довжини цілого масиву, переданого в змінну jintArray intArr, буде виглядати так:
jsize length=(*env)->GetArrayLength(env, intArr);
Виділення пам'яті під переданий масив:
jint *intArrRef=(*env)->GetIntArrayElements(env, intArr,0);
Далі з масивом intArr можна працювати як зі звичайним масивом C++. Вивільнення пам'яті з-під масиву:
(*env)->ReleaseIntArrayElements(env, intArr, intArrRef ,0);
Є аналогічні функції для доступу до елементів масивів всіх примітивних типів: GetBooleanArrayElements, GetByteArrayElements,..., GetDoubleArrayElements. Ці функції копіюють уміст масивів Java у нову область пам'яті, з якої і йде робота в C++. Для масивів об'єктів є не тільки функція GetObjectArrayElement, але й SetObjectArrayElement - для одержання й зміни окремих елементів таких масивів.
Рядок Java jstring s перетвориться в масив символів C++ так:
const char *sRef=(*env)->GetStringUTFChars(env,s,0);