Galileo Computing < openbook > Galileo Computing - Professionelle Bücher. Auch für Einsteiger.
Professionelle Bücher. Auch für Einsteiger

 << zurück
Java ist auch eine Insel von Christian Ullenboom
Programmieren für die Java 2-Plattform in der Version 5
Java ist auch eine Insel

Java ist auch eine Insel
5., akt. und erw. Auflage
1454 S., mit CD, 49,90 Euro
Galileo Computing
ISBN 3-89842-747-1
gp Kapitel 24 Java Native Interface (JNI)
  gp 24.1 Java Native Interface und Invocation-API
  gp 24.2 Einbinden einer C-Funktion in ein Java-Programm
    gp 24.2.1 Schreiben des Java-Codes
    gp 24.2.2 Compilieren des Java-Programms
    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 Nativ die Stringlänge ermitteln
  gp 24.4 Erweiterte JNI-Eigenschaften
    gp 24.4.1 Klassendefinitionen
    gp 24.4.2 Zugriff auf Attribute
  gp 24.5 Links und Weiteres


Galileo Computing

24.2 Einbinden einer C-Funktion in ein Java-Programdowntop

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

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

Galileo Computing

24.2.2 Compilieren des Java-Programms  downtop

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"

Galileo Computing

24.2.3 Erzeugen der Header-Datei  downtop

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 *jclassjstring);
#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:

gp  einem Präfix Java
gp  dem vollständigen Klassenbezeichner wobei die einzelnen Glieder im Paket durch »_« und nicht durch ».« getrennt sind
gp  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.


Galileo Computing

24.2.4 Implementierung der Methode in C  downtop

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 *jclassjstring);

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 *envjclass clazzjstring 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.


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. 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.

gp  GCC (GNU Compiler Collection), http://gcc.gnu.org/: Freier Klassiker für unzählig Plattformen
gp  Microsoft Visual C++ Toolkit 2003, http://msdn.microsoft.com/visualc/vctoolkit2003/: Übersetzer für C(++) und .NET-Programme
gp  Borland C++ Compiler, http://www.borland.com/products/downloads/download_cbuilder.html: Der BCC ist nur für Windows verfügbar

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.


Galileo Computing

24.2.6 Setzen der Umgebungsvariable  toptop

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") );
 << zurück




Copyright © Galileo Press GmbH 2005
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, Rheinwerkallee 4, 53227 Bonn, Tel.: 0228.42150.0, Fax 0228.42150.77, info@galileo-press.de