Biuletyn nr 24

Biuletyn KDM
1 | 2 | 3 | 4 | 5
6 | 7 | 8 | 9 | 10
11 | 12 | 13 | 14
15 | 16 | 17 | 18
19 | 20 | 21 | 22
23 | 24 | 25 | 26
27 | 28 | 29 | 30
31 | 32
Lista biuletynów


Spis treści

Java Native Interface (JNI) cz.2

Autor: Batosz Borucki

W poprzednim numerze biuletynu, w artykule "Java Native Interface (JNI) cz.1", przedstawione zostały metody wywoływania kodu natywnego napisanego w C/C++ z poziomu programu napisanego w Javie. Poniższy artykuł jest jego kontynuacją i pokrótce wyjaśni jak mozna odwrócić sytuację i wywoływać kod Javy z poziomu programu napisanego w C/C++.

O ile potrzeba korzystania z wydajnych kodów C/C++ w Javie wydaje się przydatna, o tyle można zastanawiać się, po co wywoływać Javę z C/C++? Najczęściej korzysta się z tego przede wszystkim w dwóch sytuacjach - gdy potrzebujemy fragmentów kodu niezależnych od platformy lub gdy chcemy skorzystać z gotowych bibliotek napisanych w Javie, czy to z powodu ich dostepności, czy funkcjonalności.

Wywoływanie kodu Java z programów C

Procedurę stworzenia programu napisanego w C/C++ i korzystającego z klas Javy można podzielić na 3 etapy:

  • napisanie kodu w Javie
  • skompilowanie klasy w Javie
  • napisanie kodu w C/C++


Kod w Javie

Postępując zatem zgodnie z powyższym planem zaczynamy od napisania kodu w Javie. W tym kroku konieczne jest utworzenie klas, z których funkcjonalności chcemy skorzystać w programie C/C++. Z poziomu kodu natywnego możliwy jest dostep do wszystkich publicznych konstruktorów oraz metod. O ile jednak, tak samo jak w samej Javie, dostęp do metod niestatycznych wymaga wcześniejszego utworzenia instancji obiektu, o tyle dostęp do metod statycznych możliwy jest bezpośrednio.


Tworzymy zatem klasę Javy o nazwie JNIHelloWorld2 zawierającą 5 metod:

  • statyczną metodę napisz - metoda ma pobierać jeden argument typu int i wypisywać tekst z podaną liczbą
  • statyczną metodę napisz2 - metoda ma pobierać jeden argument typu String i wypisywać go
  • statyczną metodę pomnoz - metoda ma pobierać dwa parametry typu double i zwracać ich iloczyn
  • statyczną metodę pokazWiadomosc - metoda ma wyświetlać okienko dialogowe z wiadomością
  • metodę dynamiczna - metoda ma wypisywać wartość zmiennej, która jest ustawiana w konstruktorze


Poniższy kod przedstawia plik JNIHelloWorld2.java:

1:  public class JNIHelloWorld2 {
2:      int i = 0;
3:    
4:      public JNIHelloWorld2() {
5: 	  this.i = 10;
6:      }
7: 
8:      public static void napisz(int n) {
9: 	   System.out.println("Hello C World number "+n);
10:     }
11:
12:     public static void napisz2(String tekst) {
13:  	   System.out.println(tekst);
14:     }
15:
16:     public static double pomnoz(double a, double b) {
17:    	   return a*b;
18:     }
19:	
20:     public static void pokazWiadomosc() {
21:        javax.swing.JOptionPane.showMessageDialog(null, "Hello C World!");
22:     }
23:	
24:     public void dynamiczna() {
25:        System.out.println("i="+i);
26:     }
27: }


Zdefiniowane zatem zostały opisane wcześniej metody oraz konstruktor.

Kompilacja klasy Java

Mając gotowy kod możemy go skompilować przy uzyciu kompilatora Javy, wydając nastepujące polecenie w konsoli (w katalogu w którym znajduje się plik JNIHelloWorld2.java):

javac JNIHelloWorld2.java


Jezeli kompilacja przebiegła pomyślnie, to nie zostaną wyświetlone żadne komunikaty, a w bieżącym katalogu pojawi się plik zawierający skompilowaną klasę - JNIHelloWorld2.class.


Kod programu natywnego w C/C++

Jesteśmy zatem w sytuacji, w której posiadamy skompilowany plik zawierający klasę Javy i chcemy wykorzystać metody tej klasy w kodzie natywnym C/C++. Zanim jednak przystapimy do pisania samego kodu musimy pamiętać o kilku podstawowych zasadach ważnych przy wywoływaniu programów Javy poprzez JNI:

  • konieczna jest znajomość nagłówków i tzw. sygnatur metod zawartych w klasie Javy, z której chcemy korzystać
  • programy Javy, do uruchomienia wymagają maszyny wirtualnej JVM, a zatem program natywny przed wywołaniem metod Javy musi uruchomić JVM
  • metody Javy korzystające ze środowiska graficznego AWT/Swing muszą być uruchamiane w środowisku umożliwiającym wyświetlanie okien (X,Windows)

O ile informacja na temat nagłówków metod jest zawarta w kodzie źródłowym, o tyle nie posiadamy takiej informacji, jeżeli mamy jedynie skompilowany plik .class. Z pomocą w obu przypadkach przychodzi nam narzędzie dostarczane w pakiecie JDK - javap. Jest to program generujący nagłówki oraz sygnatury dla zadanej klasy. Wywołujemy go zatem poleceniem:

javap -s JNIHelloWorld2

i uzyskujemy nastepujące nagłówki i sygnatury:

Compiled from "JNIHelloWorld2.java"
public class JNIHelloWorld2 extends java.lang.Object{
int i;
  Signature: I
public JNIHelloWorld2();
  Signature: ()V
public static void napisz(int);
  Signature: (I)V
public static void napisz2(java.lang.String);
  Signature: (Ljava/lang/String;)V
public static double pomnoz(double, double);
  Signature: (DD)D
public static void pokazWiadomosc();
  Signature: ()V
public void dynamiczna();
  Signature: ()V
}

Teraz znając już "definicje" klas z których będziemy korzystać możemy przystąpić do pisania kodu natywnego. Podobnie jak w przypadku poprzedniej części artykułu ograniczymy się jedynie do składni C.

Program natywny musi zatem wkonać kolejno następujące kroki, aby skorzystać z metody Javy:

  • uruchomić maszynę wirtualną
  • wczytać odpowiednią klasę
  • pobrać identyfikator metody
  • wywołać metodę


Poniższy kod przedstawia plik JNIHelloWorld2.c:

1:  #include <jni.h>
2:  #include <string.h>
3:  #include <stdio.h>
4:  
5:  #ifdef _WIN32
6:  #define PATH_SEPARATOR ';'
7:  #else
8:  #define PATH_SEPARATOR ':'
9:  #endif
10:
11: int main() {
12:  JavaVMOption options[1];
13:  JNIEnv *env;
14:  JavaVM *jvm;
15:  JavaVMInitArgs vm_args;
16:  int status;
17:
18:  jclass cls;
19:  jmethodID mid;
20:  jdouble mult;
21: 
22:  options[0].optionString = "-Djava.class.path=.";
23:  memset(&vm_args, 0, sizeof(vm_args));
24:  vm_args.version = JNI_VERSION_1_2;
25:  vm_args.nOptions = 1;
26:  vm_args.options = options;
27:
28:  status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
29: 
30:  if (status != JNI_ERR) {
31: 	   printf("JVM successfully started\n\n");	
32: 
33: 	   cls = (*env)->FindClass(env, "JNIHelloWorld2");
34: 	   if(cls != 0) {
35:
36:		   mid = (*env)->GetStaticMethodID(env, cls, "napisz", "(I)V");
37:		   if( mid != 0) {
38:			   (*env)->CallStaticVoidMethod(env, cls, mid, 5);
39:		   } else {
40:			   printf("Error loading method.\n");
41:		  }
42:
43:		   mid = (*env)->GetStaticMethodID(env, cls, "napisz2", "(Ljava/lang/String;)V");
44:		   if( mid != 0) {
45: 			   jstring str = (*env)->NewStringUTF(env, "Hello C World!");
46:			   (*env)->CallStaticVoidMethod(env, cls, mid, str);
47:		   } else {
48:			   printf("Error loading method.\n");
49:		   }
50:
51:		   mid = (*env)->GetStaticMethodID(env, cls, "pomnoz", "(DD)D");
52:		   if( mid != 0) {
53:			   mult = (*env)->CallStaticDoubleMethod(env, cls, mid, 2.0, 5.0);
54:			   printf("2*5=%f\n",mult);
55:		   } else {
56:			   printf("Error loading method.\n");
57:		   }
58:		
59:		   mid = (*env)->GetStaticMethodID(env, cls, "pokazWiadomosc", "()V");
60:		   if( mid != 0) {
61:			   (*env)->CallStaticVoidMethod(env, cls, mid);
62:		   } else {
63:			   printf("Error loading method.\n");
64:		   }
65:
66:		   mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
67:		   if( mid != 0) {
68:			   jobject obj = (*env)->NewObject(env, cls, mid);
69:			   mid = (*env)->GetMethodID(env, cls, "dynamiczna", "()V");
70:			   if( mid != 0) {
71:				   (*env)->CallVoidMethod(env, obj, mid);
72:			   } else {
73:				   printf("Error loading method.\n");
74:			   }
75:		   } else {
76:			   printf("Error loading constructor method.\n");
77:		   }
78:	   } else {
79:		   printf("Error loading Java class.\n");
80:	   }
81:	   (*jvm)->DestroyJavaVM(jvm);
82:	   return 0;
83:  } else {
84:	   printf("Error starting JVM\n");
85:	   return -1;
86:  }
87:
88: }

Zastanówmy się teraz linia po linii jak działa powyższy program natywny i jakie są składnie użytych funkcji JNI. W liniach 1-3 importujemy nagłówki użytych funkcji. W liniach 5-9 definiowany jest separator ścieżek (wykorzystayany przez JVM). Od linii 11 zaczyna się właściwy program.

Linie 12-16 to deklaracje zmiennych koniecznych do uruchomienia JVM, a zmienne zadeklarowane w liniach 18-20 będą wykorzystywane przy wołaniu metod Javy. W liniach 22-26 ustawiane są parametry konieczne do skonfigurowania uruchamianej maszyny wirtualnej - w tablicy options można zapisać parametry uruchomienia, które wraz z innymi przekazywane są poprzez strukturę vm_args. W linii 28 wywoływana jest właściwa funkcja uruchamiająca JVM z zadanymi parametrami - zwraca ona wartość typu int oznaczającą status wykonania.

Jeżeli JVM został poprawnie uruchomiony możemy przejśc do kolejnego kroku - wczytania klasy. Linia 33 to polecenie wczytujące klasę o podanej nazwie. Jeżeli klasa została wczytana poprawnie można spróbować skorzystać z jej metod - linie 36-41 to blok kodu odpowiedzialny za wywołanie metody napisz. Po pierwsze, w linii 36, pobierany jest identyfikator metody statycznej - funkcja GetStaticMethodID pobiera 4 argumenty: wskaźnik środowiska JNI (*env), klasę w której szukamy metody (cls), nazwę metody (zgodną z nagłównkiem wygenerowanym przez javap) oraz sygnaturę metody (również zgodną z wygenerowaną przez javap i reprezentującą typy pobieranych i zwracanych danych, sygnatura konieczna jest przede wszystkim do rozróżniania przeciążanych metod). Jeżeli metoda została poprawnie wczytana, w linii 38 jest ona uruchamiana poleceniem CallStaticVoidMethod (jest to jedno z poleceń z klasy CallStatic<type>Method i słuzy do wywoływania metody statycznej), pobierającym jako argumenty środowisko JNI, klasę, identyfikator metody oraz kolejne argumenty wywołania (w tym przypadku liczbę typu int).

Linie 43-49 to analogiczny blok odpowiedzialny za uruchomienie metody napisz2, linie 51-57 to uruchomienie metody pomnoz, a linie 59-64 to wywołanie metody pokazWiadomosc. Są one oparte o jednakową strukturę (pobranie identyfikatora, wywołanie metody), jednak istotne jest zwrócenie uwagi na róznice w sygnaturach wywoływanych metod oraz na uzyte do wywołania funkcje (zależne od zwracanego typu). W przypadku zmiennej typu obiektowego (a nie prostego), takiej jak np. String, w sygnaturze znajduje się pełna ścieżka klasy definiującej obiekt. Jeżeli przekazywana byłaby tablica, to sygnatura poprzedzona jest jeszcze nawiasem '['. Typy proste i void reprezentowane są odpowiednimi literami.

Linie 66-77 to skolei wykorzystanie metod niestatycznych. A zatem w pierwszej kolejności, w linii 66, pobierany jest identyfikator konstruktora obiektu JNIHelloWorld2 (nazwa przekazywana jako "<init>") o zadanej sygnaturze, a nastepnie w linii 68 tworzony jest nowy obiekt typu JNIHelloWorld2 reprezentowany po stronie natynewgo programu przez typ jobject. Funkcja NewObject pobiera środowisko JNI, klasę oraz identyfikator konstruktora i zwraca nowy obiekt (jest to analogiczne do instrukcji new JNIHelloWorld() w Javie). Mając instancję obiektu możemy wywołać jego metodę - linia 70 (funkcja CallVoidMethod jest jednym z poleceń z klasy Call<type>Method i słuzy do wywoływania metod niestatycznych),.

Na koniec programu istotne jest jeszcze zamknięcie uruchomionej maszyny wirtualnej - linia 81.


Przeglądając powyższy kod da się zauważyć jedną charakterystyczną cechę programu - za każdym razem gdy wywoływana jest funkcja JNI zwracająca jakąś wartość (np. identyfikator), wartość ta jest sprawdzana (linia 34, 37, 44, 52, 60, 67 i 70) zanim zostanie wykorzystana. Jest to zalecana technika programowania JNI. Funkcje te w przypadku błędnego wykonania zawsze zwracjają wartość 0.

Kompilacja kodu C/C++

Mając gotowy kod natywnego programu możemy go skompilować. Jest to etap, który wielu początkującym użytkownikom sprawia dużo problemów, gdyż konieczne jest podłączenie odpowiedznich bibliotek.

Powyższy kod powinien się poprawnie skompilować w systemie Linux następującym poleceniem (po wstawieniu odpowiednich katalogów):

gcc -I$JDK_PATH/include/ -I$JDK_PATH/include/linux/ -L$JDK_PATH/jre/lib/i386 -L$JDK_PATH/jre/lib/i386/server 
-ljava -ljvm JNIHelloWorld2.c -o JNIHelloWorld2

gdzie $JDK_PATH to ściezka do katalogu w który zainstalowany jest JDK.


Jeżeli kompilacja przebiegła pomyslnie, w katalogu bieżącym powinien pojawić się wykonywalny plik JNIHelloWorld2.


Jeżeli na tym etapie wystąpiły jakieś błędy podłączania bibliotek należy zweryfikować ścieżki pamiętając o tym, że dla kompilatora konieczny jest dostep do plików nagłówkowych jni.h i jni_md.h oraz bibliotek współdzielonych java oraz jvm (w systemie Linux są to pliki libjava.so oraz libjvm.so, a w systemie Windows java.dll oraz jvm.dll).

Uruchomienie programu

Tak przygotowany program jest już mozliwy do uruchomienia pleceniem:

./JNIHelloWorld2


Przy uruchamianiu programu, należy jednak pamiętać, że biblioteki java i jvm zostały zlinkowane w sposób dynamiczny, a zatem dostęp do nich jest konieczny przy uruchamianiu programu. Jeżeli wystepują błędy z tym związane, należy upewnić się, że zmienna systemowa LD_LIBRARY_PATH zawiera ścieżki do wyżej wymienionych bibliotek współdzielonych.

Jeżeli wszystko jest skonfigurowane poprawnie powinniśmy otrzymać na ekranie nastepujące komunikaty:

JVM successfully started
Hello C World number 5
Hello C World!
2*5=10.000000
i=10


oraz wyskakujące okienko dialogowe z komunikatem "Hello C world!".


Na koniec...

W dwóch częściach artykułu został przedstawiony krótki opis wykorzystania interfejsu JNI, zarówno jako wywoływanie metod natywnych C/C++ z poziomu Javy, jak i wywoływania metod Javy z poziomu programów natywnych C/C++. O ile przedstawione opisy powinny każdemu wystarczyć aby zacząć - napisać swój pierwszy program "Hello JNI World", to na pewno nie są wystarczające dla osób chcących zgłębić pełnię możliwości dostarczonych przez JNI.

Na koniec jeszcze raz kilka przydatnych i ciekawych linków do materiałów:


Porady: Operacje na wielu plikach w systemie Linux

Autor: Maciej Cytowski, Łukasz Bolikowski

Artykuł ten dedykuje osobom, które pracując z wieloma plikami dostają białej gorączki na widok komunikatu:

Argument list too long.

Problem

Problem ten pojawia się gdy próbujemy wywołać komendę, która jako argument otrzymuje długą listę plików. System próbuje wszystkie argumenty programu umieścić w przeznaczonym do tego buforze. Niestety bufor ten ma stały określony rozmiar. Jeżeli nie mieścimy się w nim to mamy kłopot, który raportowany jest przez system powyższym komunikatem.

Prześledźmy następujący przykład:


W powłoce tcsh utwórzmy katalog i 10000 plików:

mkdir example
foreach n (`seq 10000`)
   touch "example/%n.txt"
end

Problem pojawia się już przy próbie wylistowania wszystkich plików o rozszerzeniu .txt z katalogu example:

#$ ls example/*.txt
/bin/ls: Argument list too long.

Podobne kłopoty będziemy mieli przy kopiowaniu (cp), usuwaniu (rm) i innych podobnych komendach.

Rozwiązanie

Listowanie plików z katalogu example możemy wykonać za pomocą polecenia find:

find example -name "*.txt"

Aby usunąć pliki z rozszerzeniem *.txt z katalogu example musimy trochę bardziej pokombinować. Pomysł jest następujący: skoro polecenie find listuje nam pliki w katalogu to przekażmy tą listę poleceniu rm. Oczywiście naiwna próba wykonania tego pomysłu nie zadziała:

#$ rm -f `find example -name "*.txt"`  (ŹLE!)
/bin/rm: Argument list too long.

Należy tutaj użyć przekierowania ze specjalnym poleceniem klucz xargs. Dopisanie go po znaku | spowoduje przekazywanie parametrów pojedynczo jeden po drugim. Rozwiązanie wygląda zatem następująco:

#$ find example -name "*.txt" | xargs rm -f   (DOBRZE!)

Drobna uwaga dotyczy jeszcze katalogów i plików, w których nazwie znajdują się spacje (ale czy ktoś stosuje jeszcze takie nazewnictwo w Linuxie?). Powyższa komenda musi być wówczas dodatkowo uzupełniona opcją -print0 programu find oraz opcją -0 programu xargs:

#$ find example -name "*.txt" -print0 | xargs -0 rm -f   (BARDZO DOBRZE!)

Taki sam rezultat uzyskamy korzystając z opcji exec polecenia find. Komenda wymieniona po tej opcji zostanie wykonana na każdym pliku znalezionym przez find. Działa to tak:

#$ find example -name "*.txt" -exec rm -f '{}' \;  (BARDZO DOBRZE!)

Podobnie możemy postąpić przy wywoływaniu innych poleceń Linux'owych na długiej liście plików.

Przeczytaj!

  • man find
  • man xargs

Wydarzenia: Cell Programming Workshop

Autorzy: Maciej Cytowski - ICM, Maciej Remiszewski - IBM

W dniach 6-7 września 2007 w ICM odbyły się warsztaty programowania na architekturze Cell Broadband Engine ("Cell Programming Workshop"). Warsztaty prowadził dr Duc Vianey (Education Lead - Cell/Quasar Ecosystem & Solutions Enablement) z firmy IBM. Spotkanie to było częścią już ponad rocznej współpracy z firmą IBM, dotyczącej architektury i rozwiązań firmowanych znakiem Cell BE. Warsztaty wspólnie otworzyli Dyrektor Działu Systemów i Technologii IBM Polska, Pan Krzysztof Bulaszewski oraz Dyrektor ICM, Prof. Marek Niezgódka.

Wśród 28 uczestników z całej Polski znaleźli się przedstawiciele trzech centrów superkomputerowych (ICM - Warszawa, Cyfronet - Kraków, PCSS - Poznań), czterech uniwersytetów (Uniwersytet Warszawski, Politechnika Warszawska, Akademia Górniczo-Hutnicza, Uniwesytet Mikołaja Kopernika w Toruniu) oraz trzech instytutów naukowych (Międzynarodowy Instytut Biologii Molekularnej i Komórkowej - Warszawa, BioInfoBank Institute - Poznań, Instytut Informatyki UW).

W ciągu dwóch niezwykle intensywnych dni nauki uczestnicy musieli udźwignąć tonę wiedzy a rzutnik wyświetlił kilkaset slajdów. Poszczególne moduły teoretyczne przeplatane były praktycznymi sesjami "hands-on", podczas których adepci mieli okazję poznać architekturę Cell na realnych przykładach. Dzięki zdalnemu dostępowi do serwerów w laboratorium IBM w Austin studenci mogli uruchamiać swoje programy na rzeczywistym procesorze Cell jak również na symulatorze instalowanym lokalnie na stacjach roboczych.

Warsztaty można uznać za bardzo udane szczególnie dzięki kontaktom, które udało się podczas nich nawiązać. Obecnie pracujemy nad budowaniem społeczności zainteresowanych architekturą Cell, aby umożliwić wymianę wiedzy i doświadczeń pomiędzy programistami. W samym ICM-ie procesory Cell wykorzystywane są już w dwóch projektach. Jeden z nich jest projektem obliczeniowym użytkowników ICM.


Cell Workshop Welcome.jpg Cell Workshop Mdc.jpg Cell Workshop Mc2.jpg Cell Workshop Mc3.jpg
Cell Workshop Mc1.jpg Cell Workshop Agenda.jpg
Cell Workshop Mc4.jpg Cell Workshop Duc2.jpg Cell Workshop Mc5.jpg Cell Workshop Hello.jpg
Cell Workshop Duc1.jpg Cell Workshop Class2.jpg Cell Workshop Class3.jpg Cell Workshop Break.jpg
Cell Workshop Class4.jpg Cell Workshop Class5.jpg Cell Workshop Class6.jpg