Natywny interfejs Java — Java Native Interface
W projektowaniu oprogramowania , Java Native Interface ( JNI ) to platforma programowania interfejsu funkcji obcych, która umożliwia kodowi Java działającemu na wirtualnej maszynie Java (JVM) wywoływanie i wywoływanie przez aplikacje natywne (programy specyficzne dla platformy sprzętowej i systemu operacyjnego ) oraz biblioteki napisane w innych językach, takich jak C , C++ i asembler .
Cele
JNI umożliwia programistom pisanie natywnych metod do obsługi sytuacji, gdy aplikacja nie może być napisana w całości w języku programowania Java, np. gdy standardowa biblioteka klas Java nie obsługuje funkcji specyficznych dla platformy lub biblioteki programów. Służy również do modyfikowania istniejącej aplikacji (napisanej w innym języku programowania), aby była dostępna dla aplikacji Java. Wiele standardowych klas bibliotek zależy od JNI w celu zapewnienia funkcjonalności programiście i użytkownikowi, np. we/wy plików i możliwości dźwięku. Uwzględnienie implementacji API zależnej od wydajności i platformy w standardowej bibliotece umożliwia wszystkim aplikacjom Java dostęp do tej funkcji w bezpieczny i niezależny od platformy sposób.
Struktura JNI pozwala metodzie natywnej używać obiektów Java w taki sam sposób, w jaki kod Java używa tych obiektów. Metoda natywna może tworzyć obiekty Java, a następnie sprawdzać i wykorzystywać te obiekty do wykonywania swoich zadań. Metoda natywna może również sprawdzać i używać obiektów utworzonych przez kod aplikacji Java.
Tylko aplikacje i podpisane aplety mogą wywoływać JNI.
Aplikacja, która opiera się na JNI, traci przenośność platformy, jaką oferuje Java (częściowym obejściem jest napisanie oddzielnej implementacji kodu JNI dla każdej platformy i wykrycie przez Java systemu operacyjnego i załadowanie prawidłowego w czasie wykonywania).
Natywny kod może nie tylko łączyć się z Javą, ale może również korzystać z Javy Canvas
, co jest możliwe dzięki Java AWT Native Interface . Proces jest prawie taki sam, z kilkoma zmianami. Natywny interfejs Java AWT jest dostępny dopiero od wersji J2SE 1.3.
JNI umożliwia również bezpośredni dostęp do kodu asemblera , nawet bez przechodzenia przez mostek C. Dostęp do aplikacji Java z asemblera jest możliwy w ten sam sposób.
Projekt
W strukturze JNI funkcje natywne są zaimplementowane w osobnych plikach .c lub .cpp. (C++ zapewnia nieco prostszy interfejs z JNI). Gdy JVM wywołuje funkcję, przekazuje JNIEnv
wskaźnik, jobject
wskaźnik i wszelkie argumenty Java zadeklarowane przez metodę Java. Na przykład poniższy kod konwertuje ciąg Java na ciąg natywny:
extern "C"
JNIEXPORT void JNICALL Java_ClassName_MethodName
(JNIEnv *env, jobject obj, jstring javaString)
{
const char *nativeString = env->GetStringUTFChars(javaString, 0);
//Do something with the nativeString
env->ReleaseStringUTFChars(javaString, nativeString);
}
env
Wskaźnik jest strukturą, która zawiera interfejs do JVM. Zawiera wszystkie funkcje niezbędne do interakcji z JVM i pracy z obiektami Java. Przykładowe funkcje JNI konwertują natywne tablice do/z tablic Javy, konwertują natywne łańcuchy do/z Javy, tworzą instancje obiektów, rzucają wyjątki itp. Zasadniczo wszystko, co może zrobić kod Javy, można zrobić za pomocą JNIEnv
, aczkolwiek ze znacznie mniejszą łatwością.
Argument obj
jest odwołaniem do obiektu Java, w którym została zadeklarowana ta metoda natywna.
Rodzime typy danych można mapować do/z typów danych Java. W przypadku typów złożonych, takich jak obiekty, tablice i ciągi, kod natywny musi jawnie przekonwertować dane, wywołując metody w JNIEnv
.
Wskaźnik środowiska JNI ( JNIEnv* ) jest przekazywany jako argument dla każdej funkcji natywnej odwzorowanej na metodę Java, umożliwiając interakcję ze środowiskiem JNI w ramach metody natywnej. Ten wskaźnik interfejsu JNI może być przechowywany, ale pozostaje ważny tylko w bieżącym wątku. Inne wątki muszą najpierw wywołać AttachCurrentThread(), aby dołączyć się do maszyny wirtualnej i uzyskać wskaźnik interfejsu JNI. Po dołączeniu wątek natywny działa jak zwykły wątek Javy działający w ramach metody natywnej. Wątek natywny pozostaje podłączony do maszyny wirtualnej, dopóki nie wywoła funkcji DetachCurrentThread(), aby się odłączyć.
Struktura JNI nie zapewnia automatycznego wyrzucania elementów bezużytecznych dla zasobów pamięci innych niż JVM przydzielonych przez kod wykonywany po stronie natywnej. W konsekwencji natywny kod po stronie (taki jak język asemblerowy) przejmuje odpowiedzialność za jawne zwolnienie wszelkich takich zasobów pamięci, które nabywa kod natywny.
Na platformach Linux i Solaris, jeśli kod natywny zarejestruje się jako procedura obsługi sygnału, może przechwycić sygnały przeznaczone dla JVM. Łańcuch odpowiedzialności mogą być wykorzystywane w celu umożliwienia natywnego kodu do lepiej ze sobą współdziałać z JVM. Na platformach Windows można zastosować Structured Exception Handling (SEH) do zawijania kodu natywnego w blokach try/catch SEH, aby przechwytywać przerwania oprogramowania generowane przez komputer (CPU/FPU) (takie jak naruszenia dostępu do wskaźnika NULL i operacje dzielenia przez zero ).
Kodowanie używane dla funkcji NewStringUTF, GetStringUTFLength, GetStringUTFChars, ReleaseStringUTFChars i GetStringUTFRegion to „zmodyfikowany UTF-8”, który nie jest prawidłowym UTF-8 dla wszystkich danych wejściowych, ale w rzeczywistości jest to inne kodowanie. Znak zerowy (U+0000) i punkty kodowe spoza podstawowej płaszczyzny wielojęzycznej (większe lub równe U+10000, tj. te reprezentowane jako pary zastępcze w UTF-16) są kodowane inaczej w zmodyfikowanym UTF-8. Wiele programów faktycznie używa tych funkcji niepoprawnie i traktuje zwrócone lub przekazane do funkcji ciągi znaków UTF-8 jako standardowe ciągi znaków UTF-8 zamiast zmodyfikowanych ciągów znaków UTF-8. Programy powinny używać funkcji NewString, GetStringLength, GetStringChars, ReleaseStringChars, GetStringRegion, GetStringCritical i ReleaseStringCritical, które używają kodowania UTF-16LE na architekturach little-endian i UTF-16BE na architekturach big-endian, a następnie używać UTF-16 do UTF- 8 procedura konwersji.
Typy mapowania
W poniższej tabeli przedstawiono mapowanie typów między językiem Java (JNI) a kodem natywnym.
Typ C | Typ języka Java | Opis | Wpisz podpis |
---|---|---|---|
unsigned char uint8_t |
jboolean | 8 bitów bez znaku | Z |
znak podpisany int8_t |
jbyte | podpisany 8 bitów | b |
unsigned short uint16_t |
jchar | 16 bitów bez znaku | C |
krótkie int16_t |
jshort | podpisany 16 bitów | S |
int int32_t |
jint | podpisany 32 bity | i |
długi długi |
jlong | podpisany 64 bity | J |
pływak | pływać | 32 bity | F |
podwójnie | jpodwójny | 64 bity | D |
próżnia | V |
Ponadto podpis "L fully-qualified-class ;"
oznaczałby klasę jednoznacznie określoną przez tę nazwę; np. podpis "Ljava/lang/String;"
odnosi się do klasy java.lang.String
. Ponadto przedrostek [
przed podpisem tworzy tablicę tego typu; na przykład [I
oznacza typ tablicy int. Wreszcie void
podpis używa V
kodu.
Te typy są wymienne. Można używać jint
tam, gdzie zwykle używasz int
, i na odwrót, bez konieczności rzutowania typów . Jednak mapowanie między ciągami Java i tablicami na ciągi i tablice natywne jest inne. Jeśli a jstring
zostanie użyte w miejscu a char *
, kod może spowodować awarię JVM.
Wydajność
W pewnych okolicznościach JNI ponosi znaczne koszty ogólne i traci wydajność:
- Wywołania funkcji do metod JNI są kosztowne, szczególnie w przypadku wielokrotnego wywoływania metody.
- Metody natywne nie są wbudowane w maszynę JVM ani nie mogą być skompilowane JIT , ponieważ metoda jest już skompilowana.
- Tablica Java może zostać skopiowana w celu uzyskania dostępu w kodzie natywnym, a później skopiowana z powrotem. Koszt może być liniowy w rozmiarze tablicy.
- Jeśli metoda jest przekazywana jako obiekt lub musi wykonać wywołanie zwrotne, najprawdopodobniej metoda natywna będzie wykonywać własne wywołania do JVM. Dostęp do pól, metod i typów Java z kodu natywnego wymaga czegoś podobnego do refleksji . Sygnatury są określane w łańcuchach i odpytywane z maszyny JVM. Jest to zarówno powolne, jak i podatne na błędy.
- Java Strings są obiektami, mają długość i są zakodowane. Uzyskiwanie dostępu lub tworzenie ciągu może wymagać kopii O(n).
Alternatywy
Własna implementacja wirtualnej maszyny Javy ( Visual J++ ) Microsoftu miała podobny mechanizm wywoływania natywnego kodu z Javy, zwany Raw Native Interface ( RNI ). Ponadto miał łatwy sposób na wywołanie istniejącego kodu natywnego, który sam nie był świadomy Javy, takiego jak (ale nie tylko) Windows API o nazwie J/Direct . Jednak po sporze sądowym Sun-Microsoft dotyczącym tej implementacji, Visual J++ nie jest już utrzymywany.
RNI było mniej niezręczne w użyciu niż JNI, ponieważ nie było potrzebne księgowanie ze wskaźnikiem środowiska Java. Zamiast tego można było uzyskać bezpośredni dostęp do wszystkich obiektów Java. W tym celu wykorzystano narzędzie, które generowało pliki nagłówkowe z klas Javy. Podobnie J/Direct był łatwiejszy w użyciu niż użycie niezbędnej pośredniej biblioteki natywnej i JNI.
Java Native Access (JNA) to biblioteka opracowana przez społeczność, która zapewnia programom Java łatwy dostęp do natywnych bibliotek współdzielonych bez korzystania z JNI.
Zobacz też
Bibliografia
Bibliografia
- Gordon, Rob (marzec 1998). Essential Jni: Java Native Interface (1st ed.). Sala urzędnicza . P. 498. ISBN 0-13-679895-0.
- Liang, Sheng (20 czerwca 1999). Java(TM) Native Interface: Przewodnik programisty i specyfikacja (wyd. 1). Sala urzędnicza . P. 320. Numer ISBN 0-201-32577-2.
Zewnętrzne linki
- Specyfikacja Oracle JNI 6.0 API
- Natywny interfejs Java: przewodnik programisty i specyfikacja
- JNI w XCode firmy Apple
- Obsługa wyjątków w JNI
- Java Link (nowoczesny wrapper C++17 dla JNI)