Lekce 6 - JNI - Příklad v Eclipse s C++
V minulém tutoriálu o JNI, JNI - Příklad v Eclipse bez makefile a s řetězcem, jsme se naučili nativně pracovat s textovými řetězci.
Dnes vytvoříme JNI aplikaci za pomocí IDE Eclipse s balíčkem CDT. Jediný rozdíl bude, že vzniklou knihovnu .dll vytvoříme za pomoci C++. Bude využita syntaxe C++ a hlavně C++ kompilátor a linker. Postup bude proveden bez makefile (viz. předchozí kapitola), takže si budeme pomáhat trochu i příkazovým řádkem. Dále je nutno upozornit, že JNI je vyvinuto hlavně pro C. Proto neexistují přímé vazby v programové části mezi JNI a C++. Vždy je nutno využít prostředníka - Cčko. Možná vám tento díl připadne zbytečný, prakticky se dá zvládnout za pomoci analogie (mírné změny v Eclipse a MinGW) a znalostí C/C++. Přesto si myslím, že je vhodné si toto řešení předvést.
Osnova postupu
- Vytvoříme Java projekt včetně spouštěcí třídy (.java) a zkompilujeme (.class)
- Vytvoříme hlavičkový soubor (.h) a překonvertujeme do C++ projektu
- Z header filu (.h) (hlavičkového souboru) vytvoříme source file (zdrojový soubor .cpp)
- Nastavíme kompilátor a linker pro C++ v Eclipse CDT IDE
- Provedeme kompilaci a linkování dané sdílené knihovny (.dll)
- Upravíme spouštěcí Java třídu, abychom načetli nativní knihovnu a zavoláme nativní metody z Javy
1 - Tvorba a kompilace Java projektu
Jako první krok samozřejmě zkontrolujeme perspektivu. Musí být nastavená Java. Dále vytvoříme Java projekt, který si nějak pojmenujeme. Vytvoříme spouštěcí třídu bez balíčku a doplníme tělo spouštěcí metody + navrhneme nativní metody. Program spustíme a dole v konzoli si zkontrolujeme kontrolní výstup. Je to identické z předchozí kapitolou.
public class ProgramJNICpp { native void vlozInt(int cislo); native int ziskejInt(); native void posliString(String s); native String upravString(String s); public static void main(String[] args) { System.out.println("Start Programu"); } }
Nyní si trochu proberme co jsme vlastně napsali:
Co dané metody vlastně dělají? První metoda posílá int do nativní části. Druhá z nativní části integer získává. Třetí metoda posílá String do nativní části a čtvrtá metoda posílá String do nativní části a zároveň jej získává. Prakticky v této metodě provedeme úpravu vstupního Stringu. V zásadě nic složitého a jedná se metody a jejich kombinace z předchozích kapitol.
2 - Tvorba hlavičkového souboru a změna projektu
Jako další krok provedeme vytvoření hlavičkového souboru (header file .h). V případě Java9,8,7 v navigátoru si označíme adresář \bin\ a za pomocí Terminálu vyrobíme hlavičkový soubor (.h). Tento soubor okamžitě přesuneme do adresáře \src\. V případě Java10,11,12,.. vyrobíme header file v adresáři \src\ . Jako další krok provedeme změnu projektu na C/C++. Konverzi provedeme dle obrázku. Označíme projekt, který chceme zkonvertovat, a vyvoláme Popup okno -> new -> other . Objeví se dialogové okno New a vybereme C/C++. Zvolíme Convert to a C/C++ Project a nastavíme vlastnosti projektu. Dále zvolíme projekt, vybereme C++ Projekt, zvolíme Shared Library (sdílená knihovna) project a typ kompilátoru v našem případě MinGW. Postup je téměř identický jako v předchozím článku.
Po konverzi projektu vám vzniknou nějaké opět errory. Zas se jich brzy zbavíme.
3 - Vytvoření zdrojového souboru (.cpp)
Nyní si do adresáře \src\, kde je umístěn zdrojový kód Java aplikace a hlavičkový soubor, vytvoříme zdrojový kód pro knihovnu "ProgramJNICpp.cpp". Do vzniklého zdrojového souboru umístíme tento zdrojový kód. Protože využíváme C++, je nutné samozřejmě používat/naincludovat jiné knihovny. K plnému pochopení je nutná znalost C++. Rozebírat syntaxi C++ není předmětem tohoto článku. Nicméně kdo disponuje alespoň primitivními základy, tak by daný kód měl pochopit bez problému.
#include <jni.h> #include <iostream> #include <string> #include <cstring> #include "ProgramJNICpp.h" using namespace std; JNIEXPORT void JNICALL Java_ProgramJNICpp_vlozInt(JNIEnv *env , jobject obj, jint cislo){ cout << "Vypis cisla z Nativni casti : " << cislo << "\n"; } JNIEXPORT jint JNICALL Java_ProgramJNICpp_ziskejInt(JNIEnv *env, jobject obj){ jint mojeCislo=123; return mojeCislo; } JNIEXPORT void JNICALL Java_ProgramJNICpp_posliString(JNIEnv *env, jobject obj, jstring str){ // presuneme jstring na Cckovy String const char* s = env->GetStringUTFChars(str,NULL); cout << "Vypis Stringu z Nativni casti : " << s << "\n"; } JNIEXPORT jstring JNICALL Java_ProgramJNICpp_upravString(JNIEnv *env, jobject obj, jstring str){ // presuneme jstring na Cckovy String const char* s = env->GetStringUTFChars(str,NULL); // presuneme Cckovy string na C++ string string txtInterpret(s,strlen(s)); string txtNativ = " - Nativni String"; txtInterpret += txtNativ; cout << "Vypis Stringu z Nativni casti behem upravy : " << txtInterpret << "\n"; // do jstringu je nutno prevest C++ string na C string jstring vystup = env->NewStringUTF(txtInterpret.c_str()); return vystup; }
Opět si probereme co jsme to provedli. První funkce využívá pouze výpis integer hodnoty, kterou do nativní části pošleme z interpretované části. Druhá funkce vrací z nativní části pouze celočíselnou hodnotu, kterou jsme si v nativní části definovali. Třetí funkce je již zajímavá. JNI má problém v tom, že přímo nepodporuje C++ syntax. Takže je nutno vždy u složitějších datových typů provést převod nejdříve na Cčko a poté teprve na C++. Stejný problém je tedy logicky i ve funkci číslo 4. Protože se tato kapitola týká C++, tak využívám funkce C++ a tedy je nutno je samozřejmě převádět přes zprostředkující C syntax. Jen připomínám, že zde jsou JNI funkce jenž jsou podporovány v API.
4 - Nastavení kompilace a linkování v Eclipse
Opětovně provedeme nastavení kompilátoru/linkeru v Eclipse IDE. Klikneme
(označíme) projekt a stiskneme ALT+ENTER. Ve stromu si vybereme C/C++ Build
-> Settings -> a zde v Configuration nastavíme All configurations. Nyní
provedeme nastavení kompilátoru. V záložce Tool Settings -> a ve stromu
si vybereme možnost GCC C++ Compiler. Do Commandu vložíme kompilátor pro
64bit x86_64-w64-mingw32-g++
. Zkontrolujeme parametry v All options
-O3 -Wall -c -fmessage-length=0
. Ano, "g++" označuje
kompilátor/linker pro C++ a "gcc", který jsme použil v předchozím díle, je
kompilátor/linker pro Cčko.
Zde provedeme nastavení linkeru. Ve stromu se přepneme na MinGW Linker a do
Commandu opět vložíme kompilátor pro 64bit
x86_64-w64-mingw32-g++
. Do možnosti Miscellaneous doplníme do
Linker flags tento řetězec -Wl,--add-stdcall-alias
a poté se
zpět přepneme do MinGW C++ Linker.
5 - Kompilace a linkování sdílené knihovny (Build)
Nyní nás čeká provedení tvz. "buildu". Nejdříve ovšem proveďme vyčištění projektu. Toto docílíme možností Project -> Clean v menu. Označíme projekt a vypneme automatický build. Poté klikneme na známé kladívko a build se provede. Sdílená knihovna vznikne v adresáři debug, odkud ji nebudeme v Java aplikaci načítat.
6 - Upravíme Java třídu a otestujeme funkčnost projektu
Po úspěšném zbuildování *.dll knihovny se vrátíme do Javy. Danou (.dll) překopírujeme do adresáře \src\ a přejmenujeme na např. "knihovna.dll". Přepneme perspektivu a zobrazíme si zdrojový kód spouštěcí třídy v Javě. Nejdříve provede načtení knihovny (v adresáři \src\) a poté zavoláme nativní metody.
public class ProgramJNICpp { static { try { System.loadLibrary("src/knihovna"); System.out.println("Nactena knihovna knihovna.dll"); } catch(UnsatisfiedLinkError e){ System.err.println("Nelze nacist knihovnu knihovna.dll"); System.err.println(e.getMessage()); } } native void vlozInt(int cislo); native int ziskejInt(); native void posliString(String s); native String upravString(String s); public static void main(String[] args) { System.out.println("Start Programu"); ProgramJNICpp program = new ProgramJNICpp(); program.vlozInt(123123); System.out.println("Vystup int : "+program.ziskejInt()); program.posliString("Muj testovaci String"); System.out.println("Vystup String : "+program.upravString("Retezec Interpret")); } }
Při spuštění v tomto případě není nutno nastavovat VM argument
-Djava.library.path
, protože jsme .dll vložili do adresáře
\src\ a při načtení knihovny jsme daný adresář specifikovali. Java kód
knihovnu bez problémů najde. Což se ovšem nedá říci o spuštění z
command prompt, to již nechám na vás. Různé způsoby načtení (.dll) pro
různé varianty byly předvedeny v předchozích kapitolách.
Rozdíl mezi C a C++ syntaxí pří volání JNI metod přes JNIEnv *env je:
/* C syntaxe */ jsize delkaPole = (*env)->GetArrayLength(env,array); /* C++ syntaxe */ jsize delkaPole = env->GetArrayLength(array);
Příště, JNI - Primitivní datové typy, nás čekají primitivní datové typy.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkamiStaženo 410x (229.42 kB)