JNI
Od dłuższego czasu chciałem przyjrzeć się i chociażby koniuszkiem palców dotknąć JNI. Nie sięgnąłem po JNI z własnej woli, jak wiadomo JNI jest środkiem ostatecznym, czymś do czego odwołujemy się gdy brak już nadziei, gdy wszystkie mosty są spalone i próżno wypatrywać choćby najmniejszego “słoika” z remedium na nasze problemy. Właśnie w takich chwilach programiści Javy wpisują w wyszukiwarkę te trzy magiczne literki
. W kilku akapitach opowiem o tym na co warto zwrócić uwagę i gdzie natknąłem się na problemy, może to komuś pomoże.
Aby stworzyć wiązanie będziemy musieli przejść przez kilka etapów pośrednich.
- Będziemy musieli zdefiniować interfejs programistyczny w Javie,
- następnie przygotować na odpowiednie pliki nagłówkowe dla implementacji w C/C++ .
- w końcu przyjdzie czas na trochę prawdziwej implementacji. Będzie okazja, żeby sprawdzić ile pamiętasz z C/C++
.
Stworzenie API Javowego to rzecz najprostsza, znajdziecie to w każdym tutorialu np. tutaj. Wydaje mi się, że nawet nie trzeba tego arta dokładnie czytać
, jeżeli chodzi o Javo’ową stronę najważniejsze są dwie rzeczy:
- Słówko
nativektóre informuję kompilator, że implementację tej metody dostarczy zewnętrzna biblioteka natywna. Przykładowa sygnatura metody:private native void sayHello();. - Druga sprawa to załadowanie natywnej biblioteki do naszego projektu (już po uruchomieniu), zrealizować to można poprzez następujący statyczny blok kodu:
static { System.load("ścieżka\HelloWorldNative\dist\HelloWorldNative.dll"); }Możemy użyć metody
loadlubloadLibraryróżnica między nimi może okazać się istotna. W wywołaniu metodyloadpodajemy dokładną nazwę biblioteki wraz z rozszerzeniem. Wystarczy wyobrazić sobie sytuację gdy chcemy nasz program uruchomić pod linuxem, biblioteka zamiast rozszerzenia .dll ma .so. Z pomocą przychodzi nam druga wspomniana metoda,loadLibraryprzyjmuję na wejściu tylko nazwę biblioteki (bez rozszerzenia), odpowiednia biblioteka jest wyszukiwana przez jvm w lokalizacjach na które wskazuje zawartość property systemowegojava.library.path. Standardowo dojava.library.pathdodawane są (dla Windows) katalogi../Windowsoraz../Windows/System32powinien również zostać uwzględniony biezący katalog w którym jest uruchamiana aplikacja Javowa korzystająca z biblioteki.Więcej o wspomnianych metodach w dokumentacji klasy System.
Gdy mamy gotowy kod javowy, możemy wygenerować sobie plik(i) nagłówkowy które posłużą nam do implementacji kodu w C/C++.
javah.exe -o wyjsciowy.h -jni -classpath ścieżka-do-klasybuildclasses NaszaKlasa
Należy przy tym pamiętać, że javah działa na skompilowanej klasie, więc jeżeli zmienisz coś w kodzie, pamiętaj aby najpierw zmienioną klasę skompilować a dopiero potem wywołać powyższe polecenie po raz drugi.
Finalnym i najbardziej pracochłonnym krokiem jest stworzenie natywnej biblioteki pośredniczącej. Biblioteka taka musi oferować funkcje określone w wygenerowanym w poprzednim kroku pliku nagłówkowym. Omówię skrótowo jak to zrobiłem w Visual Studio 2005, polecam też:Beginning JNI with NetBeans C/C++ Pack 5.5, jeżeli zdecydujecie się użyć opisanego w tutorialu zestawu Netbeans+Cygwin może wam się przydać informacja o tym, że GCC nie obsługuje (win32+cygwin) typu “__int64″. Został użyty w deklaracji typu w plikach nagłówkowych jni, na The Graphic Muse znajdziecie dokładniejsze wytłumaczenie, wraz z eleganckim rozwiązaniem problemu
. Przejdźmy jednak do rzeczy:
- Stwórz projekt:File/NewProject/Win32Project/ po wprowadzeniu nazwy przejdziesz do dalszych stron wizarda, wybierz: DLL jako typ aplikacji i zaznacz checkbox “Export symbols”. Zatwierdź wszystko.
- Teraz będzie trochę mieszania
w właściwościach projektu, kliknij prawym na głównym węzeł projektu w “Solution Explorer”. Musimy dodać do naszego projektu katalogi z których Visual Studio ma brać pliki nagłówkowe związane z JNI. W Configuration Properties/C/C++/General/Additional Includes Directoriesdodajemy dwa katalogi:JAVAHOME/includeorazJAVAHOME/include/windows. Dodajemy również katalog w którym znajduję się wygenerowany przez nas plik nagłówkowy. - Czas w końcu uwzględnić bibliotekę do której wiążemy nasz kod javowy. Ja otrzymałem wraz z plikiem .dll, plik .lib oraz odpowiedni plik nagłówkowy .h. Plik nagłówkowy dodajemy analogicznie jak w poprzednim kroku, natomiast .lib należy uwzględnić w własnościach linkera (
Linker/Input/Additional Dependencies). - W projekcie istnieje plik nazwa-projektu.cpp, właśnie w nim będzie się działo całe zło
.
#include "jni.h" #include "windows.h" #include "plik-naglowkowy-natywnej-biblioteki.h" #include "wygenerowany-przez-nas-plik-naglowkowy.h"Pozostaje “tylko” zaimplementować odpowiednie funkcje w C++.
Jeżeli chodzi o implementację należy zwrócić uwagę na kilka istotnych kwestii:
- W C/C++ łańcuchy tekstowe (String) nie są kodowane w utf-8 jak w Javie, o tym jak to robić poprawnie można poczytać tu.
- Pamiętaj o tym, że każdy kawałek pamięci który zaalokujesz musisz zwolnić. Kod który piszesz będzie uruchomiony przez proces JVM, ale łapska GC tu nie sięgają.
- Odwoływanie się do tablic też nie jest takie proste… tu nic nie jest “taki proste”
VS2005 domyślnie buduje wersję debug naszego projektu, nalezy pamiętać, ze tak stworzony dll jest zależny od specyficznych bibliotek dostarczanych tylko z Visual Studio.
Moja wiedza jak widzicie jest dość powierzchowna, ale jak zwykle mam nadzieje, że komuś to pomoże. W moim przypadku implementacja wiązania ograniczyła się do wywołania odpowiednich funkcji z oryginalnej dll’ki, mimo, że nie było tam wielkiej filozofii to i tak to co napisałem nie zawsze działa :/. Po wiedzę konkretną odsyłam do źródeł:
- “The Java Native Interface – Programmer’s Guide and Specification” Sheng Liang
- JNI Technology – Sun’owski tutorial, całkiem sporo przykładów.
- Beginning JNI with Netbeans – wspomniany w tekście tutorial: JNI w Netbeans.
- The Graphic Muse – krok po kroku, coś jak tutaj
- JNI Basics – 1
- Integrating Visual C++,Java and Assembly – java i asm (grrr….
) - JNI tutorial
Na sam koniec zostawiłem deser, jeżeli szukaliście kiedyś narzędzia do rozwiązywania nierozwiązywalnych problemów to SWIG jest właśnie dla was. W skrócie:
SWIG is a software development tool that connects programs written in C and C++ with a variety of high-level programming languages. SWIG is used with different types of languages including common scripting languages such as Perl, PHP, Python, Tcl, Ruby and PHP.
Poniżej update który zawdzięczamy w całości Przemkowi Lewickiemu (lemekk):
Ponieważ maneo “trzasnął” kawałek dobrego posta, który w moim przypadku okazał się bardzo pomocny, postanowiłem dorzucić od siebie kilka drobnych uwag, które mogą się komuś przydać przy walce z JNI
.
Wykorzystanie bibliotek typów.
W moim przypadku, musiałem wywołać funkcję znajdującą się w windowsowej bibliotece typów (konkretnie activeds.dll). I tu zaczęły się schody, ponieważ nie było już tak prosto jak w opisie maneo
. Do biblioteki dołączony jest tylko plik .tlb. I to właśnie nim należy się posłużyć, żeby wykorzystać bibliotekę. jest to jednak trochę bardziej skomplikowane niż proste zaincludowanie pliku nagłówkowego. Oto krótka proceudurka:
- w miejscu, gdzie dołączamy pliki nagłówkowe, zamiast zwykłego #import wklepujemy coś takiego: #include “biblioteka.tlb”
- kompilujemy projekt. Kompilator powinien wygenerować kilka nowych plików w tym plik “biblioteka.tlh”.
- w miejscu #include’a wpisujemy juz zwykły: #import “biblioteka.tlh” (prawdopodobnie plik “biblioteka.tlh” będzie trzeba przenieść wcześniej z folderu ‘debug’ do folderu projektu). W trakcie kompilacji mogą wystąpić błędy związane z powtórzeniem definicji typów. Trzeba wtedy po prostu usunąć te definicje z pliku “biblioteka.tlh”.
Po takich akrobacjach powinniśmy już mieć dostęp do typów i ich funkcji w bibliotece.
Błąd: “Exception in thread “main” java.lang.UnsatisfiedLinkError: ścieżka\NaszaBiblioteka.dll:
Nie można uruchomić aplikacji, ponieważ jej konfiguracja jest niewłaściwa. Problem ten może rozwiązać ponowne zainstalowanie aplikacji”. Błąd ten występował podczas ładowania biblioteki przez Javę, na komputerach, na których nie zainstalowany został Visual. Z rozwiązaniem tego problemu pomógł mi… maneo
, a piszę o tym dlatego, że nie umieścił tego w swoim poście bo pewnie było to dość oczywiste ale dla amatorów Visuala takich jak ja pewnie już nie
. W każdym razie chodzi o konfigurację uruchomieniową (czy jak to tam zwał), z którą kompilator Visuala tworzy bibliotekę. Jak napisał maneo, domyślnie jest to konfiguracja “debug”. Czego nie napisał, to to, że należy wybrać konfigurację “release”
. Po takim zabiegu biblioteka powinna już “śmigać” wszędzie