Lekce 5 - JNI - Příklad v Eclipse bez makefile a s řetězcem
V minulém tutoriálu o Java Native Interface, JNI - Příklad v Eclipse s makefile, jsme si ukázali, jak vytvořit JNI aplikaci v IDE Eclipse CDI a použili jsme k tomu makefile.
Dnes si budeme pomáhat trochu i příkazovým řádkem. Ne každý chce totiž v Eclipse používat makefile. Opětovně je projekt téměř identický jako v předchozích dvou dílech. A abychom to trochu okořenili, nebudeme využívat jen primitivní datové typy, ale pohrajeme si i se Stringem.
Osnova postupu
- Vytvoříme Java projekt včetně spouštěcí třídy (.java) a zkompilujeme pro Java8, 7, 6, ... (`.class`)
- Vytvoříme hlavičkový soubor (*.h) a překonvertujeme do C/C++ projektu
- Z header filu (hlavičkového souboru) vytvoříme source file (zdrojový soubor *.c)
- Nastavíme kompilátor a linker 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 konzole si zkontrolujeme kontrolní výstup. Je to identické z předchozí kapitolou.
public class ProgramString { native void metodaTest(); native String posliString(String str); public static void main (String [] args) { System.out.println("Testovací výpis z Javy"); } }
Co dané metody vlastně dělají? První metoda je jasná (viz. minulé kapitoly). Druhá metoda má návratový typ String, což znamená, že nativní metoda nám přenese hodnotu typu String z nativní části programu do interpretované části programu a zároveň je String i parametr interpretované části, který se přenese do nativní. Pouze provedeme změnu toho řetězce. Prakticky je to triviální část.

2 - Vytvoření hlavičkového souboru a konverze projektu
(pro Java 9,8,7,6,...) Protože nám Eclipse neumožňuje v Project exploreru zobrazit *.class (zkompilované Java třídy) a my potřebujeme vyrobit přes "javah" hlavičkový soubor, tak budeme muset spustit navigator, který nám zobrazí *.class soubory.

V navigátoru klikneme na adresář \bin\ a za pomocí klávesové zkratky CTRL+ALT+T se nám v Eclipse zobrazí terminál. Jedná se o obyčejný command prompt, dostupný v Eclipse od verze 3.7. Díky němu vygenerujeme za pomocí příkazu soubor *.h. Přes Popup menu vyvoláme refresh projektu a všimneme si vzniku souboru ProgramString.h (header file *.h) v adresáři \bin\. Tento soubor okamžitě přesuneme do adresáře \src\.
javah -jni ProgramString
Pro Java 10,11[18.9],12[19.3],...: V adresáři \src\ spustíme terminal a zadáme tento příkaz.
javac -h . ProgramString.java

Jako další provedeme konverzi z Java Projektu na C/C++ projekt. K tomu samozřejmě nestačí jen změnit perspektivu. Konverzi provedeme dle obrázku. Označíme projekt, který chceme zkonvertovat, a vyvoláme Popup okno -> new -> other. Objeví se dialogové okno New, vybereme C/C++ -> zvolíme Convert to a C/C++ Project. Dále nastavíme vlastnosti projektu. Zvolíme projekt a 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.

Můžeme si všimnout změn Project exploreru v projektu. Projekt se překonvertovat. Samozřejmě automaticky došlo ke změně perspektivy na C/C++. Po konverzi projektu nám vzniknou nějaké errory, ale zatím si jich nevšímejte, brzy se jich zbavíme.
3 - Vytvoření zdrojového souboru C/C++
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 "ProgramString.c". Označíme daný adresář a opět vyvoláme Popup okno. Vybereme cestu New -> File -> a objeví se nám opět dialogové okno. Zvolíme adresář, kam se má vytvořit, a do File name vyplníme název.

Jakmile vytvoříme zdrojový soubor *.c a vložíme tento kód, který uložíme, tak by měly samy zmizet zmíněné errory. Doplníme hlavičky metod (funkcí) deklaracemi a vyplníme těla daných funkcí.
#include <jni.h> #include <stdio.h> #include "ProgramString.h" #include "string.h" JNIEXPORT void JNICALL Java_ProgramString_metodaTest(JNIEnv *env, jobject obj){ printf("Testovaci Vypis z Nativni casti JNI Programu"); } JNIEXPORT jstring JNICALL Java_ProgramString_posliString(JNIEnv *env, jobject obj, jstring str){ // zpracujeme vstupni retezec const char *retezec = (*env)->GetStringUTFChars(env, str, 0); printf("Zde je vlozeny retezec : %s\n", retezec); // POZOR pouze 256 znaku char veta [256]; // vlozeni retezce do pole charu strcpy(veta,"Ahoj jak se mas ?"); // slouceni retezcu v C-cku strcat(veta,retezec); jstring vystup = (*env)->NewStringUTF(env,veta); return vystup; }
Nyní si trochu probereme co jsme provedli. První funkci kvůli jednoduchosti přeskočíme. Druhá funkce již zajímavější. C-čko samotné nepodporuje String, takže je nutné jej za pomoci JNI metody (funkce) "GetStringUTFChars()" převést na pole charů. Zde je seznam všech JNI fukcí podporovaných v JNI API. Syntaxi C nebudu rozebírat. Další funkce "NewStringUTF()" vytváří String, který poté přesuneme do interpretované části, je to návratová hodnota naší funkce.

4 - Nastavení kompilátoru a linkeru
Nyní nás čeká nejnudnější, ale také nejdůležitější část. 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. Ve stromu v záložce Tool Settings
vybereme možnost GCC C Compiler a do Commandu vložíme kompilátor pro 64bit
x86_64-w64-mingw32-gcc
. Zkontrolujeme parametry v All options
-O3 -Wall -c -fmessage-length=0
. Pravděpodobně v defaultu budete
mít – O0, takže se přesuňte do Optimization a nastavte -O3. Jinak pokud
bychom nestavili PATH k jni.h a jni_md.h, do PATH bychom zde kompilátoru museli
v možnosti Includes (-I) dané paths nastavit.

Dále 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-gcc
. 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. Zde uvidíme upravený All options. (-shared
... je tam defaultně a označuje tvorbu *.dll).

Zvolíme Apply a OK. Tím máme nastaven kompilátor i builder.
5 - Kompilace do *.dll
Čeká nás tedy provedení tzv. "buildu". Nejdříve ovšem provedeme vyčištění projektu. V menu zvolíme možnost Project -> Clean ... -> 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 budeme v Java aplikaci načítat.

6 - Úprava Java třídy a volání nativní metody
Po úspěšném zbuildování *.dll knihovny se vrátíme do Javy. Přepneme perspektivu a zobrazíme si zdrojový kód spouštěcí třídy v Javě. Nejdříve provede načtení knihovny a poté zavoláme nativní metody.
public class ProgramString { static { try { System.loadLibrary("libProjektJNIEclipse"); System.out.println("Nactena knihovna libProjektJNIEclipse.dll"); } catch(UnsatisfiedLinkError e){ System.err.println("Nelze nacist knihovnu libProjektJNIEclipse.dll"); System.err.println(e.getMessage()); } } native void metodaTest(); native String posliString(String str); public static void main(String[] args) { System.out.println("Testovaci vypis z JAVY "); ProgramString program = new ProgramString(); program.metodaTest(); String s = "Isaac Asimov"; System.out.println("Zde je vystup : " +program.posliString(s)); } }

Před samotným spuštěním Java třídy je v IDE nutno opět projekt
vyčistit. (Project -> Clean). Opětovně je nutno JVM informovat, kde danou
*.dll hledat a toho docílíme nastavením v konfigurátoru spuštění. Nahoře
v menu zvolíme Run -> Run configurations ... a provedeme nastavení. Ve
stromu si najdeme spouštěcí třídu přepneme záložku Arguments -> VM
argument, kam vložíme -Djava.library.path=Debug
. Poté klikneme
na Apply a Run a uvidíme výsledek.

Využívat v kódu *.dll umístěnou v adresáři Debug není moc chytré.
Každým spouštěním probíhá kompilace a struktura adresáře Debug se
poškodí a přestane fungovat. Proto doporučuji vytvořit nový adresář,
např. "dll", a do něj knihovnu nakopírovat. Při spuštění přes IDE je
vhodné upravit VM argument -Djava.library.path=dll
. Vše by mělo
běžet v pořádku. Pokud aplikaci spouštíme z příkazového řádku přes
IDE v konzoli nebo přímo přes command prompt, nesmíme zapomenout, že když
spustím z adresáře \bin\, v tomto adresáři se adresář \dll\ nenachází a
proto je nutné říci JVM ať hledá adresář \dll\ o úroveň výše, přímo
v rootu projektu.

To je pro zatím vše, základní kostra řešení byla ukázána a jemné detaily různých datových typů, objektů, pointer vs. reference, vláken, apod., to již bude na vás.
V následujícím kvízu, Kvíz - JNI, Eclipse, konzole a .dll , si vyzkoušíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 11x (74 kB)
Aplikace je včetně zdrojových kódů v jazyce Java