2.9 Podprogramy#
Java Native Interface (JNI)#
Podprogramy w Javie to mechanizm umożliwiający wywołanie kodu napisanego w innych językach programowania (głównie C/C++) z poziomu programu Java. Służy do tego Java Native Interface (JNI).
Kiedy używać JNI#
JNI stosuje się w następujących przypadkach:
- Konieczność użycia istniejących bibliotek systemowych
- Potrzeba większej wydajności w krytycznych sekcjach kodu
- Dostęp do funkcjonalności specyficznych dla platformy
- Integracja z kodem legacy
Deklaracja metod natywnych#
public class NativeExample {
// Deklaracja metody natywnej
public native int dodaj(int a, int b);
public native String systemInfo();
// Ładowanie biblioteki natywnej
static {
System.loadLibrary("nativelib");
}
public static void main(String[] args) {
NativeExample example = new NativeExample();
int wynik = example.dodaj(5, 3);
System.out.println("Wynik: " + wynik);
String info = example.systemInfo();
System.out.println("Info: " + info);
}
}
Generowanie nagłówków#
Używając narzędzia javah (w starszych wersjach JDK):
javac NativeExample.java
javah -jni NativeExample
Zostanie wygenerowany plik NativeExample.h:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class NativeExample */
#ifndef _Included_NativeExample
#define _Included_NativeExample
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: NativeExample
* Method: dodaj
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_NativeExample_dodaj
(JNIEnv *, jobject, jint, jint);
/*
* Class: NativeExample
* Method: systemInfo
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_NativeExample_systemInfo
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
Implementacja w C#
Plik nativelib.c:
#include <jni.h>
#include "NativeExample.h"
#include <stdio.h>
#include <stdlib.h>
/*
* Implementacja metody dodaj
*/
JNIEXPORT jint JNICALL Java_NativeExample_dodaj
(JNIEnv *env, jobject obj, jint a, jint b) {
return a + b;
}
/*
* Implementacja metody systemInfo
*/
JNIEXPORT jstring JNICALL Java_NativeExample_systemInfo
(JNIEnv *env, jobject obj) {
char info[256];
sprintf(info, "System: %s, Procesor: %s",
getenv("OS"), getenv("PROCESSOR_ARCHITECTURE"));
return (*env)->NewStringUTF(env, info);
}
Kompilacja biblioteki natywnej#
Linux/Unix:#
gcc -shared -fPIC -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux \
-o libnativelib.so nativelib.c
Windows:#
gcc -shared -I"%JAVA_HOME%\include" -I"%JAVA_HOME%\include\win32" \
-o nativelib.dll nativelib.c
Praca z tablicami#
public class ArrayNative {
public native int[] sortArray(int[] array);
public native void printArray(int[] array);
static {
System.loadLibrary("arraylib");
}
}
Implementacja C:
#include <jni.h>
#include <stdlib.h>
// Funkcja porównująca dla qsort
int compare(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
JNIEXPORT jintArray JNICALL Java_ArrayNative_sortArray
(JNIEnv *env, jobject obj, jintArray array) {
// Pobranie elementów tablicy
jint *elements = (*env)->GetIntArrayElements(env, array, NULL);
jsize length = (*env)->GetArrayLength(env, array);
// Sortowanie
qsort(elements, length, sizeof(jint), compare);
// Utworzenie nowej tablicy Java
jintArray result = (*env)->NewIntArray(env, length);
(*env)->SetIntArrayRegion(env, result, 0, length, elements);
// Zwolnienie zasobów
(*env)->ReleaseIntArrayElements(env, array, elements, 0);
return result;
}
JNIEXPORT void JNICALL Java_ArrayNative_printArray
(JNIEnv *env, jobject obj, jintArray array) {
jint *elements = (*env)->GetIntArrayElements(env, array, NULL);
jsize length = (*env)->GetArrayLength(env, array);
printf("Tablica: [");
for (int i = 0; i < length; i++) {
printf("%d", elements[i]);
if (i < length - 1) printf(", ");
}
printf("]\n");
(*env)->ReleaseIntArrayElements(env, array, elements, 0);
}
Obsługa wyjątków w JNI#
JNIEXPORT jint JNICALL Java_Example_divide
(JNIEnv *env, jobject obj, jint a, jint b) {
if (b == 0) {
// Zgłoszenie wyjątku
jclass exceptionClass = (*env)->FindClass(env, "java/lang/ArithmeticException");
(*env)->ThrowNew(env, exceptionClass, "Dzielenie przez zero");
return 0;
}
return a / b;
}
Dostęp do pól i metod obiektów#
JNIEXPORT void JNICALL Java_Example_modifyObject
(JNIEnv *env, jobject obj) {
// Pobranie klasy obiektu
jclass cls = (*env)->GetObjectClass(env, obj);
// Pobranie ID pola
jfieldID fieldID = (*env)->GetFieldID(env, cls, "value", "I");
// Odczyt wartości pola
jint value = (*env)->GetIntField(env, obj, fieldID);
// Modyfikacja wartości
(*env)->SetIntField(env, obj, fieldID, value * 2);
// Wywołanie metody Java
jmethodID methodID = (*env)->GetMethodID(env, cls, "notify", "()V");
(*env)->CallVoidMethod(env, obj, methodID);
}
Zarządzanie pamięcią w JNI#
JNIEXPORT jstring JNICALL Java_Example_createString
(JNIEnv *env, jobject obj) {
// Utworzenie local reference
jstring localStr = (*env)->NewStringUTF(env, "Lokalny string");
// Utworzenie global reference (przeżyje zakończenie funkcji)
jstring globalStr = (*env)->NewGlobalRef(env, localStr);
// Usunięcie local reference
(*env)->DeleteLocalRef(env, localStr);
// Pamiętaj o usunięciu global reference gdy nie jest już potrzebny
// (*env)->DeleteGlobalRef(env, globalStr);
return globalStr;
}
Najlepsze praktyki JNI#
- Minimalizuj użycie JNI - używaj tylko gdy konieczne
- Sprawdzaj wyjątki po każdym wywołaniu JNI
- Zarządzaj pamięcią - zawsze zwalniaj zasoby
- Używaj lokalnych referencji gdy to możliwe
- Unikaj długich operacji w kodzie natywnym
- Testuj na różnych platformach
Uwaga: Użycie JNI wiąże się z utratą głównych zalet Javy - przenośności i bezpieczeństwa. Kod natywny może prowadzić do błędów segmentacji i jest specyficzny dla platformy.