21.3 Dynamische Bibliotheken erzeugen
Im zweiten Schritt kann der Java-Code übersetzt werden, denn jetzt würde eine Ausführung einen Fehler produzieren. Existiert die dynamische Bibliothek nicht oder ist sie 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.tutego.jni.StrLen.<clinit>(StrLen.java:7)
Exception in thread "main"
Für die nativen Methoden auf der Java-Seite gibt es auf der C(++)-Seite entsprechende Implementierungen. Da die Implementierung noch nicht erstellt wurde, gibt es logischerweise einen Laufzeitfehler.
21.3.1 Die Header-Datei erzeugen
Die nativ implementierten Funktionen verfügen über eine bestimmte Signatur, damit die JVM bei einem Aufruf aus der Java-Welt an die native Implementierung weiterleiten kann. Wie die Signatur aussieht, definiert die JNI-Spezifikation. Um es so einfach wie möglich zu machen, generiert ein Hilfsprogramm aus der Java-Klasse mit der nativen Methode eine C(++)-Header-Datei. Eine Header-Datei enthält Makros und Konstanten und definiert sowie dokumentiert oftmals die Funktionen, die dann später in der C(++)-Datei realisiert werden. (Ein bisschen entspricht das den Schnittstellen und den Klassen, die sie implementieren.)
Die Header-Datei gibt die Signatur der Funktionen vor und wird nicht verändert. Anschließend inkludiert das C(++)-Programm die Header-Dateien und implementiert die vorgegebene Funktion. Dann kann das C(++)-Programm übersetzt, in eine dynamische Bibliothek eingepackt und von der JVM ausgelesen werden. Kommen wir zum ersten Schritt:
Ein Generator erstellt die Header-Datei, der aus der Klassendatei die Methoden-Signatur und Rückgabe 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 21.3: build.xml, Ausschnitt
<javah classpath="bin" outputFile="strlen.h" verbose="yes">
<class name="com.tutego.jni.StrLen" />
</javah>
In diesem Beispiel soll für die Klasse StrLen die Header-Datei strlen.h generiert werden.
Hinweis |
Soll das Kommandozeilenprogramm javah benutzt werden, so bestimmt der Schalter -o den Namen der Ausgabedatei: $ javah -jni -o strlen.h com.tutego.jni.StrLen Die volle Qualifizierung ist wichtig, andernfalls gibt es merkwürdige Fehler wie "error: cannot access StrLen" und "bad class". |
An der entstandenen Header-Datei strlen.h sollten keine Änderungen vorgenommen werden. Werfen wir einen Blick hinein, damit wir wissen, welche C-Funktion wir implementieren müssen:
Listing 21.4: strlen.h
/* DO NOT EDIT THIS FILE – it is machine generated */
#include <jni.h>
/* Header for class com_tutego_jni_StrLen */
#ifndef _Included_com_tutego_jni_StrLen
#define _Included_com_tutego_jni_StrLen
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_tutego_jni_StrLen
* Method: strlen
* Signature: (Ljava/lang/String;)I
*/
JNIEXPORT jint JNICALL Java_com_tutego_jni_StrLen_strlen
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
Die Funktion heißt auf der C-Seite nicht einfach strlen(), weil wegen fehlender Namensräume sonst Verwechslungsgefahren mit anderen nativen Methoden anderer Pakete nicht ausgeschlossen sind. Aus diesem Grund enthält der Funktionsname auf der C-Seite den Paketnamen und den Namen der Java-Klasse. Dementsprechend setzt sich der Funktionsname zusammen aus:
- einem Präfix Java,
- dem vollständigen Klassenbezeichner, wobei die einzelnen Glieder im Paket durch »_« und nicht durch ».« getrennt sind, und
- dem Namen der Methode.
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.
21.3.2 Implementierung der Funktion in C
In der automatisch erzeugten Header-Datei lässt sich die Signatur der Funktion ablesen; die Basis für die Implementierung ist:
JNIEXPORT jint JNICALL Java_com_tutego_jni_StrLen_strlen(
JNIEnv *, jclass, jstring );
Wir erzeugen eine neue Datei, strlen.c, mit einer Implementierung für Java_com_tutego_ 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 21.5: strlen.c
#include <jni.h>
#include "strlen.h"
#include <stdio.h>
JNIEXPORT jint JNICALL Java_com_tutego_jni_StrLen_strlen(
JNIEnv *env, jclass clazz, jstring s )
{
printf( "Hallo Java-Freunde!\n" );
return 0;
}
Der erste Parameter, env, zeigt auf die JNI-Umgebung, über die alle JNI-Funktionen zur Verfügung stehen. Wir werden sie später nutzen.
21.3.3 Die C-Programme übersetzen und die dynamische Bibliothek überzeugen
Der C(++)-Compiler muss nun die dynamische Bibliothek übersetzen. 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 erzeugt prinzipiell jeder Compiler, 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:
- GCC (GNU Compiler Collection)
http://gcc.gnu.org/; freier Klassiker für unzählige Plattformen - Microsoft Visual C++ 2010 Express
http://www.microsoft.com/germany/express/products/windows.aspx; Übersetzer für C(++)- und .NET-Programme - C++ Compiler
https://downloads.embarcadero.com/item/24778; der Compiler ist nur für Windows verfügbar. Embarcadero hat den Compiler von Borland übernommen.
Die GNU Compiler Collection unter Cygwin
Da jeder Compiler andere Aufrufkonventionen hat, führen wir unser Beispiel mit dem GNU Compiler durch. GCC ist klassischerweise ein Unix-Compiler, doch gibt es ihn auch für Windows. Cygwin portiert die unter Unix bekannten Tools für Windows, und ein Teil der Tool-Sammlung ist der C(++)-Compiler.
Für die Installation wird zunächst unter http://www.cygwin.com/ das kleine Programm set-up.exe geladen und ausgeführt (es steckt hinter dem Link Install or update Cygwin now).
- Nach dem Start überspringe das Willkommensfenster mit Weiter >.
- Wähle den Eintrag Install from Internet aus, um aus dem Internet alle nötigen Pakete geladen zu bekommen. Wähle dann Weiter >.
- Gib bei Root Directory zum Beispiel c:\cygwin ein, und wähle Weiter >.
- Cygwin speichert die geladenen Teile erst zwischen und möchte dazu ein Verzeichnis bekommen – das Verzeichnis kann nach der Installation gelöscht werden. Vorgewählt ist der Desktop. Der Eintrag wird geändert oder so belassen. Dann Weiter >.
- Im Folgenden lassen sich Verbindungsdaten einstellen, damit das Setup-Programm die Programme aus dem Internet laden kann. Die Voreinstellung Direct Connection passt im Regelfall. Dann Weiter >.
- Wähle aus der Liste einen Server aus, von dem die Dateien geladen werden. Dann aktiviere Weiter >. Falls jetzt ein Setup Alert kommt, kann dieser mit OK geschlossen werden.
- Wähle aus dem Zweig Devel den Eintrag gcc-core (bzw. gcc-g++). (Den gdb können wir angeben, wenn C-Programme debuggt werden sollen.) Dann Weiter >. Die Wahl selektiert automatisch ein paar weitere abgeleitete Pakete und zeigt sie an. Dann Weiter >. Der tatsächliche Download beginnt.
- Es lassen sich Icons setzen. Sie sind nicht nötig und können abgewählt werden. Weiter > schließt die Installation ab.
Ü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. Somit sieht das ganze Build-Skript folgendermaßen aus:
Listing 21.6: build.xml
<project default="cc" basedir=".">
<target name="javah">
<javah classpath="bin" outputFile="strlen.h" verbose="yes">
<class name="com.tutego.jni.StrLen" />
</javah>
</target>
<target name="cc" depends="javah">
<exec dir="c:\cygwin\bin\" executable="c:\cygwin\bin\gcc-3">
<arg value="-mno-cygwin" />
<arg value="-I" />
<arg value="C:\Program Files\Java\jdk1.7.0\include" />
<arg value="-I" />
<arg value="C:\Program Files\Java\jdk1.7.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.
Ihr Kommentar
Wie hat Ihnen das <openbook> gefallen? Wir freuen uns immer über Ihre freundlichen und kritischen Rückmeldungen.