6. díl - JNI - Příklad v Eclipse s C++

Java Pro pokročilé JNI JNI - Příklad v Eclipse s C++

V minulém tutoriálu o JNI 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

  1. Vytvoříme Java projekt včetně spouštěcí třídy (.java) a zkompilujeme (.class)
  2. Vytvoříme hlavičkový soubor (*.h) a překonvertujeme do C++ projektu
  3. Z header filu *.h (hlavičkového souboru) vytvoříme source file (zdrojový soubor *.cpp)
  4. Nastavíme kompilátor a linker pro C++ v Eclipse CDT IDE
  5. Provedeme kompilaci a linkování dané sdílené knihovny (*.dll)
  6. 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");
    }
}
Nastavení Java projektu

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 filu .h). 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\. 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.

Hlavičkový soubor

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/nain­cludovat 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.

Zdroják Cpp

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.

Nastavení kompilátoru pro C++

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.

Nastavení linkeru C++

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.

Build dll

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.

Test spuštění JNI aplikace s C++

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ě nás čekají primitivní datové typy.


 

Stáhnout

Staženo 35x (229.42 kB)

 

  Aktivity (2)

Článek pro vás napsal Robert Michalovič
Avatar
viz. linkedin

Jak se ti líbí článek?
Ještě nikdo nehodnotil, buď první!


 


Miniatura
Všechny články v sekci
JNI - Java Native Interface
Miniatura
Následující článek
JNI - Primitivní datové typy

 

 

Komentáře

Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zatím nikdo nevložil komentář - buď první!