23.2 Die Schritte zur Einbindung einer C-Funktion in ein Java-Programm
Wir wollen in einem kurzen Überblick sehen, wie prinzipiell die Vorgehensweise ist. Dazu werfen wir einen Blick auf die Implementierung einer einfachen Klasse, die lediglich die Länge der Zeichenkette berechnet.
23.2.1 Schreiben des Java-Codes
Zuerst benötigen wir eine Klasse mit einer nativen Funktion. Wir haben gesehen, dass der Modifizierer native dafür nötig ist. Die Funktion besitzt, wie eine abstrakte Klasse, keine Implementierung.
public static native int strlen( String s );
Als Nächstes geben wir der Funktion einen Konstruktor, der die dynamische Bibliothek lädt:
Listing 23.1 StrLen.java
public class StrLen
{
static {
System.loadLibrary("strlen");
}
public static native int strlen( String s );
public static void main( String args[] )
{
System.out.println( strlen("Markus ist doof.") );
}
}
23.2.2 Compilieren des Java-Codes
Im zweiten Schritt kann der Java-Code übersetzt werden, doch eine Ausführung würde einen Fehler produzieren.
Beispiel Ein Java-Programm wird ausgeführt, und die dynamische Bibliothek existiert nicht (oder ist nicht im Pfad eingebunden).
|
java.lang.UnsatisfiedLinkError: no io in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1312)
at java.lang.Runtime.loadLibrary0(Runtime.java:749)
at java.lang.System.loadLibrary(System.java:820)
at StrLen.<clinit>(StrLen.java:4)
|
23.2.3 Erzeugen der Header-Datei
Die nativen Funktionen haben eine feste Signatur, damit sie die JVM bei einem Funktionsaufruf auch finden können. Diese Methoden können von Hand erstellt werden, was jedoch sehr aufwändig ist. Besser ist es, die Header-Datei mit Hilfe des Programms javah für die zu implementierende Funktion zu erstellen. Das Programm liegt dem Java-SDK bei und muss im Pfad eingetragen sein. Der Aufruf sieht wie folgt aus:
$ javah -jni -o strlen.h StrLen
Mit dem Schalter -o bestimmen wir den Namen der Ausgabedatei, die in diesem Fall strlen.h heißen soll.
An der entstandenen Header-Datei StrLen.h sollten keine Änderungen vorgenommen werden. Werfen wir dennoch einen Blick hinein, damit wir wissen, welche Methode wir implementieren müssen:
Listing 23.2 strlen.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class StrLen */
#ifndef _Included_StrLen
#define _Included_StrLen
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: StrLen
* Method: strlen
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_StrLen_strlen
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
Die Methode in C heißt nicht einfach strlen(), sondern davor steht noch das Paket, in dem sich die Methode befindet (hier das Standardpaket) und der Name der Klasse. Andernfalls gäbe es leichte Überschneidungen und Angriffspunkte.
Der Methodenname setzt sich im Wesentlichen aus folgenden Elementen zusammen:
|
Vollständiger Klassenbezeichner |
|
Trennung der einzelnen Glieder im Paket nicht durch ».« , sondern durch »_« |
|
Methodenbezeichner |
Alle primitiven Java-Typen sind auf spezielle Typen in C abgebildet, so steht jint für ein Integer und jstring für einen Pointer auf eine Zeichenkette.
23.2.4 Implementierung der Methode in C
Im automatisch erzeugten Headerfile lässt sich die Signatur der nativen Methode ablesen, die Basis für die Implementierung ist:
JNIEXPORT jint JNICALL Java_StrLen_strlen( JNIEnv *, jclass, jstring );
Wir erzeugen eine neue Datei StrLen.c. Wir wollen dabei erst einmal eine Methode implementieren, die nur etwas auf dem Bildschirm ausgibt. So können wir testen, ob überhaupt alles zusammen läuft. Anschließend kümmern wir uns um die Zeichenkettenlänge:
#include <jni.h>
#include "StrLen.h"
#include <stdio.h>
JNIEXPORT jint JNICALL Java_StrLen_strlen( JNIEnv *env, jclass clazz, jstring s )
{
printf( "Hallo Java-Freunde!\n" );
return 0;
}
Der erste Parameter der C-Funktion ist die this-Referenz. Zwar kann jede nicht statische Methode in Java automatisch this nutzen, doch die Zielsprache C weiß nichts von Objekten und auch nicht, zu welchem Objekt strlen() gehört. Daher übergibt die JVM diese Referenz an die Plattformimplementierung, und die this-Referenz zeigt auf das StrLen-Objekt.
23.2.5 Übersetzen der C-Programme und Erzeugen der dynamischen Bibliothek
Mit dem Compiler muss nun die dynamische Bibliothek übersetzt werden. Da jeder Compiler andere Aufrufkonventionen hat, führen wir das Beispiel am Compiler von Borland (kurz BCC) durch, da dieser unter http://www.borland.com/bcppbuilder/freecompiler/ frei in der Version 5.5 zum Download bereitsteht.1 Das Archiv trägt den Namen freecommandLinetools.exe, das auch mit einer FTP-Suche zu finden ist. Nach der Installation sollten wir den Pfad zum Compiler setzen. Für unsere Demo halten wir uns bei den Verzeichnissen an die Vorgabe C:\Borland\BCC55. Bevor die Arbeit beginnt, müssen wir nur noch zwei kleine Dateien anlegen, die dem Compiler und Linker die Pfade zeigen. Sie liegen beide im bin-Verzeichnis des Compilers.
Listing 23.3 bcc32.cfg
-w
-I"c:\Borland\Bcc55\include"
-L"c:\Borland\Bcc55\lib;d:\Borland\Bcc55\lib\psdk"
Listing 23.4 ilink32.cfg
-x
-L"c:\Borland\Bcc55\lib;d:\Borland\Bcc55\lib\psdk"
Nach dieser Vorbereitung lässt sich die .dll erzeugen. Um flexibel zu sein, greifen wir zu einer Batch-Datei, die den Compiler aufruft und den Linker zur .dll verpflichtet.
Listing 23.5 make.bat
bcc32 -c -Ic:/jdk1.4/include -Ic:/jdk1.4/include/win32 strlen.cpp
bcc32 -tWD strlen.obj
Wir erkennen in der Angabe der Pfade die beiden Include-Verzeichnisse aus dem SDK - das Verzeichnis ist natürlich nur ein Beispiel und muss der persönlichen Installation angepasst werden.
Die Option zum Übersetzen in eine .dll sieht unter jedem Compiler etwas anders aus. Benutzer des Visua- C++-Compilers schreiben:
cl -I"d:\jdk1.4\include" -I"d:\jdk1.4\include\win32" /LD StrLen.c
Mit dem GCC unter Unix können wir Folgendes verwenden:
gcc -Wall -shared -I /usr/local/java/include \
-I /usr/local/java/include/genunix strlen.c \
-o libstrlen.so
Hinweis Die dynamische Bibliothek muss unter Windows die Endung .dll und unter Unix-Systemen die Endung .so tragen. Unter Unix-Welt beginnen die dynamischen Bibliotheken mit dem Präfix lib, so dass sich daraus für eine Datei die Namensgebung libName.so ergibt.
|
23.2.6 Setzen der Umgebungsvariable
Beim Aufruf muss der JVM mitgeteilt werden, wo sie die dynamisch ladbaren Bibliotheken finden kann. Dazu wertet die Laufzeitumgebung die Umgebungsvariable LD_LIBRARY_PATH aus. Diese muss unter Umständen noch gesetzt werden. Befinden wir uns im selben Verzeichnis, ist das nicht nötig.
1 Es gibt noch weitere freie Compiler, etwa der bekannte GCC >http://gcc.gnu.org/), OpenWatcom >http://www.openwatcom.org/).
|