24.2 Einbinden 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.
24.2.1 Schreiben des Java-Codes
 
Zunächst benötigen wir eine Klasse mit einer nativen Funktion. Wir haben gesehen, dass dafür der Modifizierer native nötig ist. Die Funktion besitzt – wie eine abstrakte Methode – keine Implementierung.
public static native int strlen( String s );
Die Funktion soll eingebettet werden in eine Klasse mit einem statischen Initialisierungblock, der die dynamische Bibliothek lädt:
Listing 24.1
com/javatutor/jni/StrLen.java
package com.javatutor.jni;
public class StrLen
{
static {
System.loadLibrary( "strlen" );
}
public static native int strlen( String s );
}
Eine Beispielklasse soll lediglich die Funktion aufrufen.
Listing 24.2
com/javatutor/jni/StrLenDemo.java
package com.javatutor.jni;
public class StrLenDemo
{
public static void main( String[] args)
{
System.out.println( strlen("2003 UB313") );
}
}
24.2.2 Compilieren des Java-Programms
 
Im zweiten Schritt kann der Java-Code übersetzt werden, doch würde eine Ausführung einen Fehler produzieren. Existiert die die dynamische Bibliothek nicht, oder sie ist nicht im Pfad eingebunden, folgt ein Fehler wie der folgende:
java.lang.UnsatisfiedLinkError: no strlen in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1682)
at java.lang.Runtime.loadLibrary0(Runtime.java:822)
at java.lang.System.loadLibrary(System.java:992)
at com.javatutor.jni.StrLen.<clinit>(StrLen.java:7)
Exception in thread "main"
24.2.3 Erzeugen der Header-Datei
 
Für die nativen Funktionen auf der Java-Seite gibt es auf der C(++) Seite entsprechende Implementierungen. Die nativ implementierten Funktionen verfügen über eine bestimmte Signatur, damit die JVM bei einem Aufruf an die tatsächliche Implementierung weiterleiten kann.
Die Funktionen auf der C(++) werden in einer Header-Datei genannt, die später die Implementierung inkludiert. Die Header-Datei erstellt ein Generator, der aus der Klassendatei die Signatur ausliest und nach einem festen Schema die Funktionen auf der C(++)-Seite benennt. Zum Aufruf des Generators bietet sich ein Ant-Skript an, und auch das JDK bringt mit javah ein Dienstprogramm mit. Mit Ant erstellt der Task <javah> die entsprechende Header-Datei.
Listing 24.3
Ausschnitt aus build.xml
<javah classpath="bin" outputFile="strlen.h" verbose="yes">
<class name="com.javatutor.jni.StrLen" />
</javah>
In diesem Beispiel soll für die Klasse StrLen die Header-Datei strlen.h generiert werden. Die Klassendatei findet sich im Ordner out.
Soll das Kommandozeilenprogramm javah benutzt werden, so bestimmt der Schalter -o den Namen der Ausgabedatei.
$ javah -jni -o strlen.h StrLen
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.4
strlen.h
/* DO NOT EDIT THIS FILE – it is machine generated */
#include <jni.h>
/* Header for class com_javatutor_jni_StrLen */
#ifndef _Included_com_javatutor_jni_StrLen
#define _Included_com_javatutor_jni_StrLen
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_javatutor_jni_StrLen
* Method: strlen
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_javatutor_jni_StrLen_strlen
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
Die Methode heißt auf der C-Seite nicht einfach strlen(), weil es sonst Verwechslungsgefahren mit anderen nativen Methoden anderer Pakete geben könnte. Aus diesem Grund wird der Paketname und der Name der Klasse mit in den Methodennamen gegeben. Dementsprechend setzt sich der Methodenname zusammen aus:
|
einem Präfix Java |
|
dem vollständigen Klassenbezeichner wobei die einzelnen Glieder im Paket durch »_« und nicht durch ».« getrennt sind |
|
dem 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.
24.2.4 Implementierung der Methode in C
 
In der automatisch erzeugten Header-Datei lässt sich die Signatur der nativen Methode ablesen; die Basis für die Implementierung ist:
JNIEXPORT jint JNICALL Java_com_javatutor_jni_StrLen_strlen (JNIEnv *, jclass, jstring);
Wir erzeugen eine neue Datei strlen.c mit einer Implementierung für Java_com_javatutor_jni_StrLen_strlen(). Dabei soll zunächst etwas auf dem Bildschirm ausgegeben werden; wir wollen damit testen, ob überhaupt alles zusammen läuft. Anschließend kümmern wir uns um die Zeichenkettenlänge.
Listing 24.5
strlen.c
#include <jni.h>
#include "StrLen.h"
#include <stdio.h>
JNIEXPORT jint JNICALL Java_com_javatutor_jni_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 weiß die Zielsprache C 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.
24.2.5 Übersetzen der C-Programme und Erzeugen der dynamischen Bibliothek
 
Mit dem Compiler muss nun die dynamische Bibliothek übersetzt werden. Die dynamisch ladbaren Bibliotheken sind unter Windows die .dll-Dateien (dynamic link 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 befolgt.
Auf dem Markt gibt es eine Reihe guter und freier Compiler, die für die Übersetzung verwendet werden können.
Die GNU Compiler Collection
Da jeder Compiler andere Aufrufkonventionen hat, führen wir das Beispiel am GCC durch. GCC ist klassischerweise ein Unix-Compiler, doch gibt es ihn auch für Windows. Cygwin ist eine Sammlung von unter Unix bekannten Tools für Windows und ein Teil davon ist der C(++)-Compiler. Für die Installation wird zunächst unter http://www.cygwin.com/ das kleine Programm setup.exe geladen, was dann über das Internet alle nötigen Pakte nachlädt. Nach dem Starten geben wir als Root-Verzeichnis c:\cygwin an. Cygwin speichert die geladenen Teile erst zwischen und möchte dazu auch ein Verzeichnis bekommen – das Verzeichnis kann dann gelöscht werden. Beim nächsten Dialog lassen sich Pakete auswählen; uns interessiert hier nur aus dem Zweig Devel der gcc-core (bzw. gcc-g++). Die Wahl selektiert noch ein paar abgeleitete Pakete. (Den gdb können wir angeben, wenn C-Programme gedebuggt werden sollen.)
Nach der Installation können wir eine Konsole aufmachen und in das Verzeichnis C:\cygwin\bin wechseln. Der Aufruf von gcc wird nur die Fehlermeldung »gcc: no input files« ergeben sollen. Wir können nun den Suchpfad für den Compiler anpassen, doch soll der Compiler über ein Ant-Skript gesteuert werden.
Übersetzen mit Ant
Das Build-Tool Ant bringt in der Standard-Distribution keinen Task mit, der einen C-Compiler anstößt. Nichtsdestotrotz lassen sich mit <exec> externe Programme aufrufen. Damit sieht das ganze Build-Skript folgendermaßen aus:
Listing 24.6
build.xml
<project default="cc" basedir=".">
<target name="javah">
<javah classpath="bin" outputFile="strlen.h" verbose="yes">
<class name="com.javatutor.jni.StrLen" />
</javah>
</target>
<target name="cc" depends="javah">
<exec dir="c:/cygwin/bin/" executable="c:/cygwin/bin/gcc">
<arg value="-mno-cygwin" />
<arg value="-I" />
<arg value="C:/Programme/Java/jdk1.5.0/include" />
<arg value="-I" />
<arg value="C:/Programme/Java/jdk1.5.0/include/win32" />
<arg value="-shared" />
<arg value="-Wl,--add-stdcall-alias" />
<arg value="-o" />
<arg value="${basedir}/strlen.dll" />
<arg value="${basedir}/strlen.c" />
</exec>
</target>
</project>
Hinweis Die dynamische Bibliothek muss unter Windows die Endung .dll und unter Unix-Systemen die Endung .so haben. In der Unix-Welt beginnen die dynamischen Bibliotheken mit dem Präfix lib, sodass sich daraus für eine Datei die Namensgebung libName.so ergibt.
|
Wer den <exec>-Task nicht verwenden mag, der kann auch die externen CC-Tasks unter http://ant-contrib.sourceforge.net/ nutzen.
24.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.
Welche Pfade nun durchsucht werden, zeigt die folgende einfache Zeile:
System.out.println( System.getProperty("java.library.path") );
|