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 JNIEnvwskaźnik, jobjectwskaź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);
}

envWskaź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 objjest 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
int64_t

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 [Ioznacza typ tablicy int. Wreszcie voidpodpis używa Vkodu.

Te typy są wymienne. Można używać jinttam, 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 jstringzostanie 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

Zewnętrzne linki