Przy okazji próbowania się z IntelliJ IDEA zmierzę się też z Java Native Interface. Generalnie rzecz ujmując JNI pozwala wywołać z poziomu kodu Java funkcje ze skompilowanych do kodu natywnego bibliotek napisanych w C, C++ i Asemblerze. Nie można co prawda wywołać dowolnej funkcji z natywnej biblioteki, tylko funkcję o wygenerowanej przez Javę sygnaturze, ale już w ciele tej funkcji możemy wywoływać co chcemy. Reasumując przebieg wydarzeń wygląda następująco:
1. Piszemy metodę w kodzie Java o sygnaturze native
2. Generujemy sygnaturę tej metody w C/C++ (nie wiem, co z assemblerem)
3. Piszemy ciało metody o wygenerowanej sygnaturze, tu jeżeli jest taka potrzeba, wywołujemy w nim dowolne metody z bibliotek napisanych w C/C++
Co daje JNI? Niektórzy twierdzą, że większą wydajność, ale spory, czy Java jest mniej wydajna od C/C++ są burzliwe, a do tego dochodzi narzut na wywołanie metody przez JNI, więc na główną zaletę ta cecha nie pasuje.
Istnieje niestety ciągle wiele bibliotek napisanych w C/C++ które nie mają i pewnie długo jeszcze nie będą miały odpowiedników w Javie i tu moim zdaniem pole do popisu dla JNI. Mnie przynajmniej po to skorzystanie z JNI było potrzebne.
Wady JNI? Jak się zaraz przekonacie... Jakikolwiek błąd w kodzie biblioteki powoduje wywalenie całej wirtualnej maszyny, bez czytelnych komunikatów błędu. Generalnie nie wiadomo co się dzieje. Jak nie trzeba, lepiej się w JNI StraBe nie pakować.
Ale do rzeczy...
Tworzę nowy projekt o nazwie Jni
Oprócz katalogu src utworzę w nim katalog c na pliki źródłowe w kodzie C.
Zaczynam od stworzenia klasy pl.matt.jni.HelloJNI
package pl.matt.jni;
/**
* Mateusz Zięba
* 2008-06-19 02:27:56
*/
public class HelloJNI {
private static native void say(String greeting);
static {
System.load("/home/mateusz/priv/IdeaProjects/Jni/c/hello.so");
}
public static void main(String args[]) {
say("Ala ma kota");
}
}
Warto zauważyć tutaj 2 rzeczy:
metodę natywną private static native void say(String greeting), która zostanie zaimplementowana w bibliotece napisanej w języku C,
oraz blok
static {
System.load("/home/mateusz/priv/IdeaProjects/Jni/c/hello.so");
}
gdzie ni mniej ni więcej tylko ta biblioteka napisana w C, której jeszcze nie ma, ale która zaraz będzie, zostaje załadowana przez JVM.
To by było tyle w Javie.
Wygeneruję teraz sygnaturę metody. W tym celu przechodzę do katalogu ze skompilowanymi klasami... Niestety taki nie istnieje. Klikam więc prawym przyciskiem myszy (PPM) na klasie HelloJNI i z menu kontekstowego wybieram pozycję Compile 'HelloJNI'. Pojawił się katalog out. W nim podkatalogi test i production. Trochę nie rozumiem tej struktury katalogów generowanej przez IntelliJ IDEA, ale może kiedyś zrozumiem. W katalogu out/production/Jni generuję plik nagłówkowy C.
javah -jni pl.matt.jni.HelloJNI
brak komunikatu oznacza sukces. W bieżącym katalogu pojawił się plik pl_matt_jni_HelloJNI.h, który przenoszę do katalogu c
mv pl_matt_jni_HelloJNI.h ../../../c/
W dużym projekcie to wszystko oraz następne rzeczy robiłby oczywiście makefile, ale jakoś sobie bez niego tym razem poradzę.
plik pl_matt_jni_HelloJNI.h wygląda tak:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class pl_matt_jni_HelloJNI */
#ifndef _Included_pl_matt_jni_HelloJNI
#define _Included_pl_matt_jni_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: pl_matt_jni_HelloJNI
* Method: say
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_pl_matt_jni_HelloJNI_say
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
na jego podstawie tworzę plik hello.c
#include "pl_matt_jni_HelloJNI.h"
#include <jni.h>
#include <stdio.h>
JNIEXPORT void JNICALL Java_pl_matt_jni_HelloJNI_say
(JNIEnv * env, jclass class, jstring jstr) {
jboolean iscopy;
char* greeting = (*env)->GetStringUTFChars(env, jstr, &iscopy);
printf(greeting);
}
Nie rozwodząc się, ta funkcja napisana w C, skonwertuje przekazany z kodu Java parametr na ciąg znaków char*
char* greeting = (*env)->GetStringUTFChars(env, jstr, &iscopy);
i wyświetli go na ekranie. Mam nadzieję.
Kompiluję plik hello.c
mateusz@mateusz-laptop:~/priv/IdeaProjects/Jni/c$ gcc -I"/usr/lib/jvm/java-6-sun/include"
-I"/usr/lib/jvm/java-6-sun/include/linux" -c hello.c -o hello.o
hello.c: W funkcji `Java_pl_matt_jni_HelloJNI_say,:
hello.c:8: ostrzeżenie: inicjalizacja odrzuca kwalifikatory z typu docelowego wskaźnika
Jakieś ostrzeżenie... nie znam się za bardzo na programowaniu w C więc nic nie jestem w stanie z nim zrobić. Jedziemy dalej.
Tworzę bibliotekę hello.so:
gcc -shared -o hello.so hello.o
Właśnie do tego pliku hello.so odwołuję się w poliku HelloJNI w sekcji
static {
System.load("/home/mateusz/priv/IdeaProjects/Jni/c/hello.so");
}
Tym razem bez ostrzeżenia.
Uff... To koniec. Odpalam klasę HelloJNI
/usr/lib/jvm/java-6-sun/bin/java -Didea.launcher.port=7532 -Didea.launcher.bin.path=/opt/idea/bin -Dfile.encoding=UTF-8 -classpath /usr/lib/jvm/java-6-sun/jre/lib/plugin.jar:/usr/lib/jvm/java-6-sun/jre/lib/rt.jar:/usr/lib/jvm/java-6-sun/jre/lib/resources.jar:/usr/lib/jvm/java-6-sun/jre/lib/javaws.jar:/usr/lib/jvm/java-6-sun/jre/lib/deploy.jar:/usr/lib/jvm/java-6-sun/jre/lib/charsets.jar:/usr/lib/jvm/java-6-sun/jre/lib/jce.jar:/usr/lib/jvm/java-6-sun/jre/lib/management-agent.jar:/usr/lib/jvm/java-6-sun/jre/lib/jsse.jar:/usr/lib/jvm/java-6-sun/jre/lib/ext/sunpkcs11.jar:/usr/lib/jvm/java-6-sun/jre/lib/ext/sunjce_provider.jar:/usr/lib/jvm/java-6-sun/jre/lib/ext/localedata.jar:/usr/lib/jvm/java-6-sun/jre/lib/ext/dnsns.jar:/home/mateusz/priv/IdeaProjects/Jni/out/production/Jni:/opt/idea/lib/idea_rt.jar com.intellij.rt.execution.application.AppMain pl.matt.jni.HelloJNI
Ala ma kota
Process finished with exit code 0
Ala ma kota, czyli wszystko działa. Napis został wyświetlony w języku C a nie w Javie.
Na koniec mały bug z IntelliJ IDEA - okienko do zmiany nazwy pliku hello.c.
Tram po środku są literki - nowa nazwa pliku.
PS. znacie jakiś dobry synonim słowa "natywny"?
Brak komentarzy:
Prześlij komentarz