Universität Bayreuth
Mathematisches
Institut
Einleitung
Erste Schritte
Mail und News
Drucken
KDE
LaTeX/TeX
Linksammlung
Linuxtools
Netzwerk
Programmieren
Windows
X Window
Anträge
Kontakt
|
Java
Nutzung des Java Native Interface (JNI)
Im nun anschliessenden Text wollen wir uns mit der Verwendung des
Native-Interfaces (Schnittstelle zu Funktionen/Programmen entwickelt in
anderen Programmiersprachen) von Java beschäftigen.
Es bietet dem Java-Programmierer die Möglichkeit, auf Features des
Betriebsystems (oder der Betriebsystemsprache)
zuzugreifen, welche in Java nicht angeboten werden. Die Liste solcher Features dürfte zu umfangreich ausfallen, weswegen hier auf
eine Auflistung verzichtet wird.
Denkbar ist z.B. der Fall, dass ein neues Programm in Java entwickelt wird, das Funktionalität verlangt, die teilweise bereits als C-Code
vorliegt. Nun kann es langwierig oder zumindest lästig sein, den ganzen
alten C-Code nach Java umzuschreiben. Und genau für solche Fälle
ist es gut, dass es das Java Native Interface (JNI) gibt.
Zunächst folgt eine allgemeine Anleitung zum Vorgehen und daraufhin wollen wir uns die Theorie anhand eines sehr
einfachen Beispiels verinnerlichen.
Wir werden also sehen wie wir aus einer C/C++ Datei eine
dynamische
Library
("*.so"-Datei unter Linux bzw. eine "*.dll"-Datei
unter Windows)
und die darin enthaltenen Methoden aus einem Java-Programm aufrufen.
Unter jedem Punkt der allgemeinen beschriebenen Schritte gibt es
einen Link zum jeweils konkreten Beispiel, in dem in der Shell
ein Kommando aufgerufen wird.
-
Erzeugen der Java-Datei
Zunächst erzeugen wir eine ganz gewöhnliche Java-Datei,
die eine öffentliche (public) Klasse enthält.
In dieser Klasse deklarieren wir nun eine
public native-Methode mit beliebigem Namen
(der Name unterliegt selbstverständlich
allen sonst auch in Java bekannten Einschränkungen), z.B.
public native void printline();
Darüberhinaus formulieren wir einen klassenweiten Ladeaufruf
System.loadLibrary("mylib");
der noch zu erzeugenden Library namens "mylib.so"
(Linux) bzw. "mylib.dll" (Windows).
Nun fehlt nur noch eine main(..)-Methode, in der
wir ein Objekt "caller" unserer neuen Klasse
erzeugen und anschliessend durch
"caller.printline();"
die native Methode aufrufen.
Screenshot:
Java Datei
-
Erzeugen der Header-Datei
Nun übersetzen wir die Klasse mit dem Java-Compiler
(im Beispiel: "javac NativeCaller.java")
und
generieren anschließend eine für unsere Library
notwendige Header-Datei (im Beispiel entsteht die Datei
"NativeCaller.h").
Dazu nutzen wir das Tool javah aus dem J2SDK.
Diesem Tool übergeben wir den Namen der soeben erzeugten
Klasse (im Beispiel: "javah NativeCaller").
Screenshots:
Übersetzen und Header erzeugen
-
Erzeugen der C-Datei
Zunächst legen wir eine neue C-Quellcode-Datei (im
Beispiel "mylib.c") an.
Wir müssen beim Schreiben der Funktionen bis auf wenige
Details nichts Spezielles
beachten, wir gehen also im Wesentlichen genauso vor wie
wir es für jedes C-Programm machen.
Mit Präprozessorincludes ergänzen wir die Header
"jni.h" sowie das im Schritt zuvor von
javah erzeugte Header-File (sowie natürlich alle
anderen Headerfiles, die wir zur Realisierung unserer
C-Funktion benötigen). Im Beispiel lauten diese:
#include <jni.h>
#include "NativeCaller.h"
Statt des normalen Funktionskopfes
void printline(void)
verwendet man allerdings den durch javah
erzeugten Funktionskopf, im Beispiel:
JNIEXPORT void JNICALL Java_NativeCaller_printline(JNIEnv *env, jobject obj)
Was wir allerdings nicht benötigen, ist eine
"main(..)"-Funktion in der C-Datei, da das
Programm ja nicht selbstständig laufen soll,
sondern nur Funktionen daraus aus einem anderen
(bereits laufenden) Programm aufgerufen werden sollen.
Screenshot:
C-Datei
-
Erzeugen der dynamischen Library
Wir übersetzen das eben erzeugte C-Programm in eine
dynamische Bibliothek (kein Executable erzeugen, geht
ohne main(..)-Funktion sowieso nicht!).
-
Unter Linux mit dem GCC-Compiler:
# Man ersetze "$JAVA_HOME" durch das Installationsverzeichnis des J2SDK,
# z.B. durch "/usr/java/j2sdk1.4.2_01",
# und "srcname.c" durch den Namen des C-Sourcefiles sowie
# "[libname]" durch den Namen der dynamischen Library
gcc -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux srcname.c -o lib[libname].so
# im Beispiel:
gcc -shared -I$JAVA_HOME/include -I$JAVA_HOME/include/linux mylib.c -o libmylib.so
Durch die Option "-shared" sagen wir dem
Compiler, dass eine shared Library (so = shared
object) erzeugen werden soll (im Beispiel
"libmylib.so").
Sollten für die Ausführung der Funktion/en der
Bibliothek noch andere Bibliotheken benötigt werden,
müssen diese natürlich schon hier dazugelinkt
werden.
Screenshot:
Dynamische Lib erzeugen
-
Unter Windows mit dem frei verfügbaren
cl-Compiler von Microsoft
(Download z.B. über das
Visual C++ Toolkit 2003):
rem Man ersetze "%JAVA_HOME%" durch das Installationsverzeichnis des J2SDK,
rem z.B. durch "C:\Programme\j2sdk1.4.2_01",
rem und "srcname.c" durch den Namen des C-Sourcefiles sowie
rem "[libname]" durch den Namen der dynamischen Library
cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD srcname.c -Fe [libname].dll
rem Im Beispiel:
cl -I%JAVA_HOME%\include -I%JAVA_HOME%\include\win32 -LD mylib.c -Fe libmylib.dll
-
Starten der Java-Applikation
Jetzt brauchen wir unser Java-Programm nur noch auszuführen.
Dem Java-Interpreter übergeben wir dabei
folgende Option
"-Djava.library.path=."
(neben dem Klassennamen selbst natürlich ;-) ).
Dadurch teilen wir dem Interpreter mit, dass das aktuelle
Verzeichnis nach dynamischen Libraries durchsuchen soll.
Und das erzeugte Libraryfile "*.so"/"*.dll"
liegt ja im aktuellen Verzeichnis. Im Beispiel lautet
der Befehl:
java -Djava.library.path=. NativeCaller
Wichtig ist hier zu erwähnen, dass man dieses Argument
auch dann nicht weglassen kann, wenn das aktuelle
Verzeichnis "." im CLASSPATH (Environmentvariable
für den Suchpfad von Java-Klassen) enthalten ist.
Unter Linux reicht es auch, die Environmentvariable
LD_LIBRARY_PATH zu setzen und die obige Option
für java wegzulassen:
# Falls Environmentvariable LD_LIBRARY_PATH leer oder nicht vorhanden war:
export LD_LIBRARY_PATH="."
# ansonsten:
export LD_LIBRARY_PATH="$LD_LIBRARY_PATH:."
java NativeCaller
Screenshot:
Java-Programm starten
Wenn die Funktion in C++ statt in C realisiert wird, geht man
analog vor.
top
Downloads
Files für das 1. Beispiel:
Java-File,
C-File,
erzeugtes C-Header-File
Variante des 1. Beispiels mit static-Methode:
Java-File,
C-File,
erzeugtes C-Header-File
Änderungen in der Beschreibung:
Deklaration der C-Funktion in Java-Applikation:
public static native void printline();
Funktionskopf der C-Funktion im C-Sourcecode:
JNIEXPORT void JNICALL Java_NativeCallerStatic_printline(JNIEnv *env, jclass cl)
Der Aufruf der nativen C-Funktion ändert sich in
"printline();" (es wird kein Objekt der
Java-Applikationsklasse mehr gebraucht).
2. Beispiel mit Funktionsargumenten:
Im gzip-ten tar-Archive
"jni_ex_02.tar.gz" findet man
ein weiteres Beispiel, in dem der nativen Funktion ein String
und ein int-Wert übergeben werden.
Der int-Wert steht dabei für die Anzahl, wie oft der
übergebene String in der Funktion ausgegeben werden soll.
3. Beispiel mit Funktionsrückgabe:
Im gzip-ten tar-Archive
"jni_ex_03.tar.gz" findet sich
ein Beispiel, in dem die native Funktion einen String
(den sie vorher vom User einliest) an das aufrufende
Java-Programm zurückgibt.
|