Útmutató a JNI-hez (Java Native Interface)

1. Bemutatkozás

Mint tudjuk, a Java egyik fő erőssége a hordozhatóság - vagyis ha egyszer írunk és fordítunk kódot, ennek a folyamatnak eredménye a platformtól független bytecode.

Egyszerűen fogalmazva, ez bármely olyan gépen vagy eszközön futtatható, amely képes a Java virtuális gép futtatására, és olyan zökkenőmentesen fog működni, mint amire számíthattunk.

Néha azonban valójában olyan kódot kell használnunk, amelyet natív módon fordítottak le egy adott architektúrához.

A natív kód használatának néhány oka lehet:

  • Néhány hardver kezelésének szükségessége
  • Teljesítménynövelés nagyon igényes folyamathoz
  • Egy létező könyvtár, amelyet újra fel akarunk használni ahelyett, hogy újraírnánk Java-ba.

Ennek elérése érdekében a JDK hidat vezet be a JVM-ben futó bájtkód és a natív kód között (általában C vagy C ++ nyelven írják).

Az eszköz neve Java Native Interface. Ebben a cikkben meglátjuk, hogyan lehet vele kódot írni.

2. Hogyan működik

2.1. Natív módszerek: A JVM megfelel az összeállított kódnak

A Java biztosítja a anyanyelvi kulcsszó, amely arra utal, hogy a módszer megvalósítását natív kód biztosítja.

Normál esetben natív futtatható program készítésekor választhatunk statikus vagy megosztott lib-ek használatát:

  • Statikus lib-ek - az összes könyvtár bináris fájl a futtatható fájl részeként lesz benne az összekapcsolási folyamat során. Így már nem lesz szükségünk a libekre, de ez növeli a futtatható fájl méretét.
  • Megosztott libek - a végső futtatható fájl csak a libekre hivatkozik, magára a kódra nem. Megköveteli, hogy az a környezet, amelyben a futtatható fájlt futtatjuk, hozzáférjen a programunk által használt összes lib fájljához.

Ez utóbbi értelmes a JNI számára, mivel nem keverhetjük a bytecode-ot és a natív módon lefordított kódot ugyanabba a bináris fájlba.

Ezért a közös libünk külön fogja megőrizni a natív kódot .so / .dll / .dylib fájl (attól függően, hogy melyik operációs rendszert használjuk) ahelyett, hogy az osztályaink részei lennének.

A anyanyelvi kulcsszó átalakítja módszerünket egyfajta absztrakt módszerré:

private native void aNativeMethod ();

Azzal a fő különbséggel ahelyett, hogy egy másik Java osztály implementálná, elkülönített natív megosztott könyvtárban valósítja meg.

Egy táblázatot készítünk a memóriában az összes natív módszerünk megvalósításához, így meghívhatók a Java kódunkból.

2.2. Szükséges alkatrészek

Itt van egy rövid leírás a legfontosabb összetevőkről, amelyeket figyelembe kell vennünk. Ezeket a cikk későbbi részében elmagyarázzuk

  • Java Code - osztályaink. Legalább egyet tartalmaznak anyanyelvi módszer.
  • Natív kód - natív módszereink tényleges logikája, általában C vagy C ++ kódolással.
  • JNI fejlécfájl - ez a C / C ++ (tartalmazza / jni.h a JDK könyvtárba) tartalmazza a JNI elemek összes definícióját, amelyeket felhasználhatunk natív programjainkban.
  • C / C ++ Compiler - választhatunk a GCC, a Clang, a Visual Studio vagy bármely más tetszés szerint, amennyiben natív megosztott könyvtárat képes létrehozni a platformunkhoz.

2.3. JNI elemek a kódban (Java és C / C ++)

Java elemek:

  • „Natív” kulcsszó - amint már kitértünk rá, minden natívként megjelölt módszert natív, megosztott libben kell végrehajtani.
  • System.loadLibrary (String libname) - statikus módszer, amely megosztott könyvtárat tölt be a fájlrendszerből a memóriába, és exportált funkcióit elérhetővé teszi a Java kódunk számára.

C / C ++ elemek (sok közülük a jni.h)

  • JNIEXPORT - a függvényt a megosztott lib-be exportálhatóként jelöli, így bekerül a függvénytáblába, és így a JNI megtalálja
  • JNICALL - kombinálva JNIEXPORT, biztosítja, hogy módszereink elérhetőek legyenek a JNI keretrendszer számára
  • JNIEnv - olyan struktúrákat tartalmazó struktúra, amelyek natív kódunkkal használhatják a Java elemek elérését
  • JavaVM - egy struktúra, amely lehetővé teszi számunkra, hogy manipuláljuk a futó JVM-et (vagy akár újat is indítsunk), hozzáadva szálakat hozzá, megsemmisítve stb.

3. Hello World JNI

Következő, nézzük meg, hogyan működik a JNI a gyakorlatban.

Ebben az oktatóanyagban a C ++ -ot használjuk anyanyelvként, a G ++ -t pedig fordítóként és linkerként.

Bármely más fordítót használhatunk preferenciánknak, de a G ++ telepítésének módja az Ubuntu, a Windows és a MacOS rendszeren:

  • Ubuntu Linux - futtassa a parancsot „Sudo apt-get install build-essential” terminálban
  • Windows - Telepítse a MinGW-t
  • MacOS - parancs futtatása „G ++” egy terminálban, és ha még nincs meg, akkor telepíti.

3.1. Java osztály létrehozása

Kezdjük az első JNI programunk létrehozását egy klasszikus „Hello World” megvalósításával.

Először létrehozzuk a következő Java osztályt, amely tartalmazza a natív módszert, amely elvégzi a munkát:

csomag com.baeldung.jni; public class HelloWorldJNI {static {System.loadLibrary ("natív"); } public static void main (String [] érvel) {new HelloWorldJNI (). sayHello (); } // Nyújtson be egy olyan natív módszert, hogy sayHello (), amely nem kap érveket, és visszaküldi a void private native void sayHello () -t; }

Ahogy látjuk, statikus blokkba töltjük a megosztott könyvtárat. Ez biztosítja, hogy készen álljon, amikor szükségünk van rá, és bárhonnan, ahol szükségünk van rá.

Alternatív megoldásként ebben a triviális programban ehelyett betölthetnénk a könyvtárat közvetlenül a natív módszer meghívása előtt, mert máshol nem használjuk a natív könyvtárat.

3.2. Módszer megvalósítása C ++ nyelven

Most létre kell hoznunk a natív módszerünk megvalósítását a C ++ nyelven.

A C ++ - on belül a definíció és a megvalósítás általában a .h és .cpp fájlokat ill.

Első, A módszer definíciójának megalkotásához a -h a Java fordító zászlaja:

javac -h. HelloWorldJNI.java

Ez generálja a com_baeldung_jni_HelloWorldJNI.h fájl az osztályban szereplő összes natív módszerrel, amelyet paraméterként adtak át, ebben az esetben csak egy:

JNIEXPORT érvénytelen JNICALL Java_com_baeldung_jni_HelloWorldJNI_sayHello (JNIEnv *, jobject); 

Mint láthatjuk, a függvény neve automatikusan létrejön a teljesen minősített csomag, osztály és metódus név használatával.

Valami érdekes, amit észrevehetünk, hogy két paramétert kapunk a funkciónkhoz; mutató az áramra JNIEnv; és azt a Java objektumot is, amelyhez a metódus kapcsolódik, a mi példányunkat HelloWorldJNI osztály.

Most létre kell hoznunk egy újat .cpp fájl végrehajtása a köszönj funkció. Itt fogunk végrehajtani olyan műveleteket, amelyek a „Hello World” szót nyomtatják a konzolra.

Megnevezzük a nevünket .cpp a fejlécet tartalmazó .h nevű fájl, és adja hozzá ezt a kódot a natív függvény megvalósításához:

JNIEXPORT érvénytelen JNICALL Java_com_baeldung_jni_HelloWorldJNI_sayHello (JNIEnv * env, jobject thisObject) {std :: cout << "Hello a C ++ -tól !!" << std :: endl; } 

3.3. Összeállítás és összekapcsolás

Ezen a ponton minden szükséges alkatrész megvan a helyén, és kapcsolatunk van közöttük.

Fel kell építenünk megosztott könyvtárunkat a C ++ kódból, és futtatnunk kell!

Ehhez a G ++ fordítót kell használnunk, nem felejtve el a JNI fejléceket a Java JDK telepítésünkből.

Ubuntu verzió:

g ++ -c -fPIC -I $ {JAVA_HOME} / tartalmazza -I $ {JAVA_HOME} / include / linux com_baeldung_jni_HelloWorldJNI.cpp -o com_baeldung_jni_HelloWorldJNI.o

Windows verzió:

g ++ -c -I% JAVA_HOME% \ include -I% JAVA_HOME% \ include \ win32 com_baeldung_jni_HelloWorldJNI.cpp -o com_baeldung_jni_HelloWorldJNI.o

MacOS verzió;

g ++ -c -fPIC -I $ {JAVA_HOME} / tartalmazza -I $ {JAVA_HOME} / tartalmazza / darwin com_baeldung_jni_HelloWorldJNI.cpp -o com_baeldung_jni_HelloWorldJNI.o

Miután összeállítottuk a platformunk számára a kódot a fájlban com_baeldung_jni_HelloWorldJNI.o, be kell építenünk egy új megosztott könyvtárba. Bármit is döntünk a megnevezésről, az a módszerbe átadott érv System.loadLibrary.

Mi a "natív" -nak neveztük el a sajátunkat, és betöltjük, amikor futtatjuk a Java-kódunkat.

Ezután a G ++ linker összekapcsolja a C ++ objektumfájlokat áthidalott könyvtárunkkal.

Ubuntu verzió:

g ++ -shared -fPIC -o libnative.so com_baeldung_jni_HelloWorldJNI.o -lc

Windows verzió:

g ++ -shared -o native.dll com_baeldung_jni_HelloWorldJNI.o -Wl, - add-stdcall-alias

MacOS verzió:

g ++ -dynamiclib -o libnative.dylib com_baeldung_jni_HelloWorldJNI.o -lc

És ez az!

Most a parancssorból futtathatjuk programunkat.

Azonban, hozzá kell adnunk a teljes elérési utat ahhoz a könyvtárhoz, amely az imént létrehozott könyvtárat tartalmazza. Így a Java tudni fogja, hol keresse meg natív libjeinket:

java -cp. -Djava.library.path = / NATIVE_SHARED_LIB_FOLDER com.baeldung.jni.HelloWorldJNI

Konzol kimenet:

Üdv a C ++ -tól !!

4. A Speciális JNI-szolgáltatások használata

A köszönés szép, de nem túl hasznos. Általában szeretnénk adatokat cserélni a Java és a C ++ kód között, és ezeket az adatokat a programunkban kezelni.

4.1. Paraméterek hozzáadása natív módszereinkhez

Hozzáadunk néhány paramétert a natív módszereinkhez. Hozzunk létre egy új osztályt Példa ParaméterekJNI két natív módszerrel, különböző típusú paramétereket és hozamokat használva:

magánhonos bennszülött hosszú összes számok (int első, int második); magánhonos String sayHelloToMe (karakterlánc neve, logikai isNő);

Ezután ismételje meg az eljárást egy új .h fájl létrehozásához „javac -h” betűvel, ahogyan azt korábban tettük.

Most hozza létre a megfelelő .cpp fájlt az új C ++ módszer megvalósításával:

... JNIEXPORT jlong ​​JNICALL Java_com_baeldung_jni_ExampleParametersJNI_sumIntegers (JNIEnv * env, jobject thisObject, jint first, jint second) {std :: cout << "C ++: A beérkezett számok a következők:" << első << "és" << második NewStringUTF (teljesNév.c_str ()); } ...

Használtuk a mutatót * env típusú JNIEnv hozzáférni a JNI környezeti példány által biztosított módszerekhez.

JNIEnv lehetővé teszi számunkra, hogy ebben az esetben átadjuk a Java-t Húrok a C ++ kódunkba, és hátralép, anélkül, hogy aggódna a megvalósítás miatt.

Ellenőrizhetjük a Java és a C JNI típusok egyenértékűségét az Oracle hivatalos dokumentációjában.

Kódunk teszteléséhez meg kell ismételnünk az előző összes fordítási lépését Helló Világ példa.

4.2. Objektumok használata és Java-módszerek hívása natív kódból

Ebben az utolsó példában azt fogjuk megtudni, hogyan lehet manipulálni a Java objektumokat natív C ++ kódunkba.

Elkezdjük egy új osztály létrehozását Felhasználói adat amelyet néhány felhasználói információ tárolására használunk:

csomag com.baeldung.jni; public class UserData {public String name; nyilvános kettős egyensúly; public String getUserInfo () {return "[név] =" + név + ", [egyenleg] =" + egyenleg; }}

Ezután létrehozunk egy másik Java osztályt PéldaobjektumokJNI néhány natív módszerrel, amelyekkel típusú objektumokat kezelünk Felhasználói adat:

... nyilvános natív UserData createUser (karakterlánc neve, kettős egyensúly); public native String printUserData (UserData felhasználó); 

Még egyszer hozzuk létre a .h fejléc, majd a natív módszerek C ++ megvalósítása egy új .cpp fájl:

JNIEXPORT jobject JNICALL Java_com_baeldung_jni_ExampleObjectsJNI_createUser (JNIEnv * env, jobject thisObject, jstring name, jdouble balance) {// Készítse el a UserData osztály objektumát jclass userDataClass = env-> FindClata / "felhasználói / ba / Ba; jobject newUserData = env-> AllocObject (userDataClass); // Állítsa be a beállítandó UserData mezőket jfieldID nameField = env-> GetFieldID (userDataClass, "név", "Ljava / lang / String;"); jfieldID balanceField = env-> GetFieldID (userDataClass, "balance", "D"); env-> SetObjectField (newUserData, nameField, név); env-> SetDoubleField (newUserData, balanceField, balance); return newUserData; } JNIEXPORT jstring JNICALL Java_com_baeldung_jni_ExampleObjectsJNI_printUserData (JNIEnv * env, jobject thisObject, jobject userData) {// Keresse meg a jclass nevű Java-módszer azonosítóját userDataClass = env-> GetDobataClass (user jmethodID methodId = env-> GetMethodID (userDataClass, "getUserInfo", "() Ljava / lang / String;"); jstring eredmény = (jstring) env-> CallObjectMethod (userData, methodId); visszatérési eredmény; } 

Ismét a JNIEnv * env mutatóval érheti el a szükséges osztályokat, objektumokat, mezőket és módszereket a futó JVM-ből.

Normál esetben csak a teljes osztály nevét kell megadnunk a Java osztály eléréséhez, vagy a helyes metódus nevét és aláírását az objektum metódus eléréséhez.

Még az osztály egy példányát is létrehozzuk com.baeldung.jni.UserData natív kódunkban. Miután megvan a példány, az összes tulajdonságát és módszerét a Java reflektáláshoz hasonló módon manipulálhatjuk.

Ellenőrizhetjük a JNIEnv az Oracle hivatalos dokumentációjába.

4. A JNI alkalmazásának hátrányai

A JNI áthidalásának vannak buktatói.

A fő hátrány az alapul szolgáló platformtól való függőség; lényegében elveszítjük az „egyszer írj, fuss bárhova” Java jellemzője. Ez azt jelenti, hogy új platformot kell készítenünk a platform és az architektúra minden új kombinációja számára, amelyet támogatni akarunk. Képzelje el, hogy ez milyen hatással lehet az összeállítási folyamatra, ha támogatjuk a Windows, Linux, Android, MacOS…

A JNI nemcsak egy komplexitási réteget ad programunkhoz. Emellett költséges kommunikációs réteget is hozzáad a JVM-be futó kód és a natív kód között: át kell alakítanunk a Java és a C ++ között mindkét módon kicserélt adatokat egy átfogó / unmarhaling folyamatban.

Néha még nincs is közvetlen átalakítás a típusok között, ezért meg kell írnunk az egyenértékünket.

5. Következtetés

A kód összeállítása egy adott platformra (általában) gyorsabbá teszi, mint a bytecode futtatása.

Ez hasznos lesz, ha fel kell gyorsítanunk egy igényes folyamatot. Akkor is, ha nincs más alternatívánk, például amikor egy eszközt kezelő könyvtárat kell használnunk.

Ennek azonban ára van, mivel minden támogatott platformhoz további kódot kell fenntartanunk.

Ezért általában jó ötlet csak akkor használja a JNI-t, ha nincs alternatív Java.

Mint mindig, a cikk kódja elérhető a GitHubon.


$config[zx-auto] not found$config[zx-overlay] not found