środa, 18 czerwca 2008

pierwsze kroki z Java Native Interface

Jako, że stałem się szczęśliwym posiadaczem IntelliJ IDEA przyszedł czas na jego wypróbowanie. Nigdy nie przepadałem za interfejsem użytkownika pisanym w SWINGu, a takowy posiada to IDE, ale może czas się do niego przekonać. W końcu wychodzi na to, że szansa na popularne IDE z interfejsem w SWINGu jest taka sama, jak na prezydenta Polski o imieniu Lech (66%).



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: