Galileo Computing <openbook>
Galileo Computing - Programming the Net
Galileo Computing - Programming the Net


Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 1.4
Buch: Java ist auch eine Insel - Zum Katalog
gp Kapitel 24 Java Native Interface (JNI)
  gp 24.1 Java Native Interface und Invocation-API
  gp 24.2 Die Schritte zur Einbindung einer C-Funktion in ein Java-Programm
    gp 24.2.1 Schreiben des Java-Codes
    gp 24.2.2 Kompilieren des Java-Codes
    gp 24.2.3 Erzeugen der Header-Datei
    gp 24.2.4 Implementierung der Methode in C
    gp 24.2.5 Übersetzen der C-Programme und Erzeugen der dynamischen Bibliothek
    gp 24.2.6 Setzen der Umgebungsvariable
  gp 24.3 Erweiterung unseres Programms

Kapitel 24 Java Native Interface (JNI)

Ein klassisches Werk ist ein Buch,
das die Leute loben, aber nie lesen.
– Ernest Hemingway (1899–1961)

Java kann nicht wirklich eine plattformunabhängige Programmiersprache sein – spätestens, wenn es an die tatsächlichen Ressourcen eines Rechners geht, müssen diese in Java eingebunden werden. Ein Sortierverfahren oder eine Datenstruktur ist wirklich plattformneutral, aber für das Bearbeiten von Dateien oder das Setzen von Punkten auf dem Bildschirm ist die jeweilige Architektur zuständig. Dazu ruft Java die nativen Methoden auf, die nicht in Java implementiert sind. Native Methoden sind in der API-Dokumentation nicht zu erkennen, aber ein Blick in den Quellcode verrät sie schnell.

Beispiel Die Methode read() aus der Klasse FileInputStream liest ein Byte aus einer Datei ein. Die Funktion ist nicht in Java implementiert.
public native int read() throws 
IOException;


Galileo Computing

24.1 Java Native Interface und Invocation-API  downtop

Um diese Schnittstelle zwischen Java und konkreter Plattform kümmert sich das Java Native Interface. Mithilfe von JNI können aus der Java Virtual Machine heraus plattformspezifische Funktionen verwendet werden. Auch umgekehrt funktioniert dieser Weg: Ein C-Programm kann über die so genannte Invocation-API auf Java-Programme zugreifen. Mit diesen beiden Teilen kann eine Migration eines Altsystems nach Java realisiert werden. Mögliche Teile wie die grafische Benutzeroberfläche werden in Java implementiert und die übrigen Teile werden in der herkömmlichen Sprache belassen, ein Wrapper jedoch wird über die Altlasten gelegt. Damit ist die alte Applikation lauffähig und Schritt für Schritt können die Altlasten abgebaut werden. Ein Teil kann bestehen bleiben, wenn zum Beispiel eine gekaufte Bibliothek mit eingebunden werden soll oder Teile nicht unter Java implementiert werden können. Natürlich sind wir dann mit Java nicht mehr wirklich plattformunabhängig, da auch die native Bibliothek auf der neuen Plattform immer neu übersetzt werden muss.

Liegt Programmcode in einer anderen Programmiersprache als Java vor – wir gehen im Folgenden von C(++) aus –, dann müssen diese zu einer dynamisch ladbaren Bibliothek gebunden werden. Diese Bibliothek implementiert die nativen Methoden, hält sich jedoch an spezielle Namenskonventionen. Das bedingt, dass es nicht möglich ist, beliebige Bibliotheken in Java einzubinden, die auf dem System schon vorhanden sind. Es lässt sich zum Beispiel unter Windows nicht einfach twain.dll verwenden, um Scanner anzusteuern.

Die dynamisch ladbaren Bibliotheken sind unter Windows die .dll-Dateien (dynamic linked libraries) und unter UNIX Dateien mit der Endung .so (shared objects). Die .dll- und .so-Dateien können mit einem beliebigen Compiler erzeugt werden, wobei zu beachten ist, dass jeder Compiler andere Aufrufkonventionen besitzt. Unter dem GNU gcc Compiler veranlasst etwa die Option shared den Compiler dazu, eine .so-Datei zu erzeugen und der Borland C++-Compiler möchte die Option -tWD dazu sehen.

Ist die dynamische Bibliothek vorhanden, muss sie noch in Java eingebunden werden. Dazu existiert die Methode System.loadLibrary(), die die Bibliothek zur Laufzeit in die Java Virtual Machine einbindet. Jeder Aufruf der nativen Funktion wird dann an die C–Funktion weitergeleitet.


Galileo Computing

24.2 Die Schritte zur Einbindung einer C-Funktion in ein Java-Programm  downtop

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.


Galileo Computing

24.2.1 Schreiben des Java-Codes  downtop

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 24.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("Sandra ist doof.") );
}
}

Galileo Computing

24.2.2 Kompilieren des Java-Codes  downtop

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)

Galileo Computing

24.2.3 Erzeugen der Header-Datei  downtop

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 aufwendig 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 24.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 Standard-Paket ist) und der Name der Klasse. Andernfalls gäbe es leichte Überschneidungen und Angriffspunkte. Der Methodenname setzt sich im Wesentlichen aus folgenden Elementen zusammen:

gp  Vollständiger Klassenbezeichner
gp  Trennung der einzelnen Glieder im Paket nicht durch ».« , sondern durch »_«
gp  Methodenbezeichner

Alle primitiven Java-Typen sind auf spezielle Typen in C abgebildet, so steht jint für ein Integer und jstring als Pointer auf eine Zeichenkette.


Galileo Computing

24.2.4 Implementierung der Methode in C  downtop

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;
}

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 Java virtuelle Maschine diese Referenz an die Plattformimplementierung, und die this-Referenz zeigt auf das StrLen-Objekt.


Galileo Computing

24.2.5 Übersetzen der C-Programme und Erzeugen der dynamischen Bibliothek  downtop

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 in der Version 5.5 frei zum Download unter http://www. borland.com/bcppbuilder/freecompiler bereitsteht. 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 24.3   bcc32.cfg
-w
-I"c:\Borland\Bcc55\include"
-L"c:\Borland\Bcc55\lib;d:\Borland\Bcc55\lib\psdk"
Listing 24.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 24.5   make.bat
bcc32 -c -Ic:/jdk1.3/include -Ic:/jdk1.3/include/win32 
strlen.cpp
bcc32 -tWD strlen.obj

Wir erkennen in der Angabe der Pfade die beiden Include-Verzeichnisse aus dem SDK.

Mit dem GCC unter Unix könnten wir Folgendes verwenden:

gcc -Wall -shared -I /usr/local/java/include 
\
-I /usr/local/java/include/genunix strlen.c \
-o strlen.so

Galileo Computing

24.2.6 Setzen der Umgebungsvariable  downtop

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.


Galileo Computing

24.3 Erweiterung unseres Programms  toptop

Wir haben unsere Funktion noch nicht zu Ende geführt. Es fehlt die Berechnung der Zeichenketten-Länge. Dazu müssen wir den String zuerst von der Unicode-Implementierung in ein C-Zeichenfeld überführen. Dazu dient eine Funktion GetStringUTFChars, die wir über die Umgebung env nutzen können.

jbyte* str = env->GetStringUTFChars( 
s, NULL );

Die Zeichenkettenlänge liefert die Funktion strlen(), die im Header string.h definiert ist:

#include <jni.h>
#include "strlen.h"
#include <stdio.h>
#include <string.h>

JNIEXPORT jint JNICALL Java_StrLen_strlen( JNIEnv *env, jclass clazz, jstring s )
{
// printf( "Hallo Java-Freunde!\n" );

if ( s == NULL )
{
jclass exc = env->FindClass( "java/lang/NullPointerException" );

if ( exc != NULL )
env->ThrowNew(exc, "(in C++ code)");

return -1;
}

const jbyte* str = env->GetStringUTFChars( s, NULL );

if ( str == NULL )
return -1;

int len = strlen( str );

env->ReleaseStringUTFChars(s, str);

return (jint) len;
}

Mit dem JNI lassen sich unter C Java-Objekte erzeugen und zerstören, auf Java-Methoden zugreifen und Parameter austauschen. Wir nutzen dass, um ein Ausnahme-Objekt zu erzeugen, wenn es keine Referenz auf ein String-Objekt gibt.

Eine intensivere Auseinandersetzung mit diesem Thema findet sich auf den Webseiten von Sun unter http://java.sun.com/docs/books/tutorial/native1.1.

  

Java 2




Copyright © Galileo Press GmbH 2002
Für Ihren privaten Gebrauch dürfen Sie die Online-Version natürlich ausdrucken. Ansonsten unterliegt das <openbook> denselben Bestimmungen, wie die gebundene Ausgabe: Das Werk einschließlich aller seiner Teile ist urheberrechtlich geschützt. Alle Rechte vorbehalten einschließlich der Vervielfältigung, Übersetzung, Mikroverfilmung sowie Einspeicherung und Verarbeitung in elektronischen Systemen.


[Galileo Computing]

Galileo Press GmbH, Gartenstraße 24, 53229 Bonn, fon: 0228.42150.0, fax 0228.42150.77, info@galileo-press.de