IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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

  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 - JNI - Java Native Interface

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.

Hlavičkový soubor - JNI - Java Native Interface

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 - JNI - Java Native Interface

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++ - JNI - Java Native Interface

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++ - JNI - Java Native Interface

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 - JNI - Java Native Interface

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++ - JNI - Java Native Interface

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ínkami

Staženo 408x (229.42 kB)

 

Předchozí článek
JNI - Příklad v Eclipse bez makefile a s řetězcem
Všechny články v sekci
JNI - Java Native Interface
Přeskočit článek
(nedoporučujeme)
JNI - Primitivní datové typy
Článek pro vás napsal Robert Michalovič
Avatar
Uživatelské hodnocení:
Ještě nikdo nehodnotil, buď první!
Programuji převážně v Javě SE,EE a trochu nativním C a CUDA. více viz.https://cz.linkedin.com/in/robert-michalovic
Aktivity