Java Native Interface

Werbung
Java Native Interface
Eine Einführung
Cornelius Zühl
[email protected]
01.03.2010
Inhaltsverzeichnis
1.
Einführung......................................................................................................................... 3
2.
Was ist JNI? ..................................................................................................................... 3
3.
JNI, was habe ich damit zu tun? ................................................................................... 4
4.
Schlüsselwort „native“, warum kann ich es verwenden? .......................................... 5
5.
Nativ (wie C++) spricht Java über die Invocation-API ............................................... 6
6.
Ein Blick hinter die Kulissen........................................................................................... 8
7.
Wie verwende ich JNI in meinem Java Programm? ................................................ 10
8.
Zusammenfassung........................................................................................................ 13
9.
Quellen ............................................................................................................................ 14
2
1. Einführung
Ziel dieser Ausarbeitung ist es einen allgemeinen Überblick über die
Funktionsweise und Verwendung des JNI zu geben. Dabei wird das
Thema zuerst motiviert. Dann werden auf die Gründe für die
Verwendung und die Arten des Einsatzes eingegangen. Zum Schluss
folgt eine kurze Zusammenfassung.
2. Was ist JNI?
Das JNI (Java Native Interface) ist eine nicht Java-spezifische API
(Application Programming Interface Programmierschnittstelle). Dem
JNI liegt aber auch eine Standardisierung zugrunde. Dieser Standard
garantiert zwei wichtige Eigenschaften:
• Kompatibilität Jede beliebige JVM lässt sich von
außerhalb auf exakt der gleichen Form betreiben.
• Gleichheit Es wird nur eine einzige API in allen JVMs
ausgeführt. Das heißt, dass einmal erzeugter binärer Code
für eine JVM auf einem bestimmten Hardwaretyp auch auf
allen JVMs dieses Hardwaretyps verwendet werden kann.
JNI bildet die Schnittstelle zwischen der JVM (Java Virtual Mashine) und
der nativen Umgebung, wie in C++ geschriebene Programmbibliotheken,
außerhalb von Java.
Die nativen Bibliotheken können die Schnittstelle nutzen, um eine JVM
zu erzeugen und so mit der Java – Welt zu interagieren.
3
3. JNI, was habe ich damit zu tun?
Nun, jeder der Java Programme einsetzt hat indirekt mit JNI zu tun.
Schon die Java – VM verwendet intern JNI. So wird z.B. bei der
Ausführung einer .jar – Datei (die zip – komprimiert ist) der Inhalt zuerst
temporär dekomprimiert um die .class – Dateien zu laden. Egal ob unter
Windows oder Unix, um diese Aufgabe zu erledigen wird eine
betriebssystemabhängige Implementierung verwendet.
Unter Windows wird die zip.dll geladen.
Unter Unix wird die libzip.so geladen.
4
Aber auch jeder der schon einmal selbst Java Programme geschrieben
hat ist indirekt mit JNI in Berührung gekommen. Viele Methoden aus
dem Java – Framework wie z.B. bei der Klasse „System“ verwenden
intern das JNI.
4. Schlüsselwort „native“, warum kann ich es verwenden?
Es gibt grundsätzlich zwei Gründe für den Einsatz des JNI aus Java
heraus. Wobei ein Grund ehr ein „scheinbarer“ Grund für die
Verwendung darstellt:
• Performancesteigerung erreichen
• Spezielle Eigenheiten des Betriebsystems verwenden
Grund 1 – Performancesteigerung erreichen
Zum einen könnte eine Geschwindigkeitssteigerung durch JNI
erzielt werden. Denn es werden einige Aufgaben der Java –
Release nicht in derselben Weise wie optimierte C/C++ –
Programme ausgeführt. Das könnte konkret bedeuten, dass
kritische Stellen wie innere Schleifen über einen native – Aufruf
ausgelagert werden.
Grund 2 – Spezielle Eigenheiten des Betriebsystems verwenden
Zum anderen könnte so der Zugang zu speziellen Fähigkeiten des
Rechners / Betriebssystems bereitet werden. Dazu zählen unter
5
vielen
anderen
Möglichkeiten
der
Anschluss
an
neue
Peripheriegeräte und Steckkarten. Der Zugriff auf verschiedene
Netztypen und die Verwendung von eindeutigen Merkmalen des
Betriebssystems. Ein Konkretes Beispiel wäre das Erfassen von
Echtzeitton über Mikrofon. Dafür gibt es im Java – Framework
keine generische Bibliothek.
Es war davon die Rede, dass ein Grund ein „scheinbarer“ Grund sei.
Aber welcher ist das? Die Performancesteigerung ist ein verbreiteter
Grund wenn man an den Gebrauch von JNI denkt. Dieser ist aber der
scheinbare Grund. Denn wer wirklich echtzeitkritische Anwendungen will,
wird nicht auf Java setzen. Bei der Deutschen Post AG wird
beispielsweise ein System eingesetzt um die Zieladressen auf Briefen zu
parsen und entsprechend Weichen auf den Wegen zu stellen den die
Briefe weiter nehmen. Dafür stehen bei den hohen Geschwindigkeiten
zum Erkennen nur wenige Millisekunden zur Verfügung. Für diesen
Einsatzbereich wird man bestimmt auf andere Programmiersprachen als
Java setzen. Außerdem werden viele zeitkritische Probleme ohnehin
schon in der JVM nativ gelöst (siehe „System.arraycopy(…)“ weiter
oben). Besser ist es die Energie auf ein gutes Design von Klassen und
Methoden zu verwenden, sie abstrakt und wieder verwendbar zu
machen. Wenn das Programm seine Vorgaben erfüllt, kann als letzter
Schritt die Geschwindigkeitssteigerung angegangen werden.
5. Nativ (wie C++) spricht Java über die Invocation-API
Unter C++ kann mit Einbinden der Java – Headerdatei (#include
<jni.h>) der Zugang zu einer eigenständigen JVM geschaffen werden.
6
Natürlich muss das JDK (Java Development Kit) installiert sein.
Ausserdem
müssen
Includeverzeichnisse
in
des
dem
JDK
Projekt
die
zusätzlichen
eingebunden
werden
(C:\Programme\Java\jdkVERSION\include;C:\Programme\Java\jdkVERS
ION\include\win32).
#include <jni.h>
/* alle Java bezogenen Typen
und Methoden sind hier
definiert */
. . .
JavaVM
*jvm
/* Pointer auf die JVM */
JNIEnv
*e;
/* Pointer auf das native
method interface */
JDK1_1InitArgs
vm_args;
/* JDK 1.1 VM Init-Argumente */
. . .
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* setzt Standard-Argumente */
vm_args.classpath = ...;
/* können geändert werden */
JNI_CreateJavaVM(&jvm, &e, &vm_args);
/* lädt und initialisiert eine
VM */
jclass
jc
= (*e)->FindClass("Main"); /* findet Klasse innerhalb der
VM */
jmethodID jmID = (*e)->GetStaticMethodID(jc, "test", "(I)V");
(*e)->CallStaticVoidMethod(jc, jmID, 100); /* ruft in der Klasse eine
Methode auf */
(*jvm)->DestroyJavaVM();
/* beendet die VM */
Das obige Beispiel zeigt wie aus C++ heraus eine JVM erzeugt wird. In
der VM wird unter Verwendung des „classpath“ nach einer Klasse
namens „Main“ (im defaultpackage) gesucht. Dann wird in dieser Klasse
die Methode „test“ aufgerufen. Die Signatur "(I)V" verrät, dass die
Methode einen Integer als Prameter verlang und „void” zurückgibt.
Es können mit Hilfe der VM
• Java Objekte erzeugt, untersucht und geändert
• Java Methoden aufgerufen
• Exceptions geworfen und gefangen
7
• Klassen und Klasseninformationen abgerufen
• Typen zur Laufzeit geprüft
werden.
6. Ein Blick hinter die Kulissen
Der Aufruf von nativ in Richtung JVM erfolgt über den Interface Pointer.
Der Interface Pointer ist dabei ein Zeiger auf ein Array von
Funktionszeigern. Der Typ von JNIEnv ist ein struct welches die Zeiger
auf die verschiedenen Funktionen der JVM enthält:
struct
void
void
void
void
...
JNINativeInterface_ {
*reserved0;
*reserved1;
*reserved2;
*reserved3;
jfieldID (JNICALL *GetStaticFieldID)
(JNIEnv *env, jclass clazz, const char *name, const char *sig);
…
} /* nur ein Auszug, weil in JNIEnv_ auf dieses struct bezug genommen wird */
struct JNIEnv_ {
const struct JNINativeInterface_ *functions;
...
jfieldID GetStaticFieldID(jclass clazz, const char *name,
const char *sig) {
return functions->GetStaticFieldID(this,clazz,name,sig);
}
…
} /* das ist nur ein Auszug um die Struktur zu verdeutlichen */
8
Die Methoden in JNIEnv sind für das jeweilige Betriebssystem unter dem
das JDK installiert ist entsprechend angepasst und kompiliert.
Auf der nativen Seite erhalten Methoden immer zwei zusätzliche
Argumente unabhängig von ihrer Parameterliste:
• Argument eins ist der JNI Interface Pointer (JNIEnv)
• Argument zwei ist bei static native Methoden
o Zeiger auf die Java – Klasse in der JVM
• Argument zwei ist bei nonstatic native Methoden
o Zeiger auf das Java – Objekt („this“) in der JVM
Weiterhin ist das Speichermanagement ein interessanter Punkt der hier
kurz beleuchtet werden soll. Wenn Java eine native Funktion aufruft,
dann werden die primitiven Datentypen (wie int, long, double usw.)
kopiert. Java – Objekte hingegen werden in lokale bzw. globale
Referenzen beim Aufruf der nativen Methode unterteilt.
Die lokalen Referenzen leben im Scope ihres erzeugenden Stack-Frame
und Thread. Sie werden bei verlassen der nativen Methode freigegeben.
Zu beachten ist hierbei, dass die JVM nur begrenzten Speicher zur
Verfügung hat um sich diese lokalen Referenzen zu merken!
Die globalen Referenzen leben bis sie explizit freigegeben werden. In
JNIEnv
existieren
Methoden
um
lokale
Referenzen
in
globale
umzuwandeln. Jede JNI Funktion die beim Aufruf Referenzen zurückgibt
wird diese als lokal zurückgeben.
Ein weiterer Punkt sind Fehlerbehandlung und Exceptions. Bei jedem
Aufruf auf nativer Seite von JNI Funktionen können Exceptions erzeugt
werden. Die JNI Funktionen geben in diesen Fällen Fehlercodes aus die
über die Methode „ExceptionOccured()“ abgerufen werden können. Bei
der Fehlerbehandlung gibt es grundsätzlich zwei Möglichkeiten um
9
darauf zu reagieren (neben der dritten Möglichkeit nichts machen).
Falls nicht auf der nativen Seite auf den Fehler reagiert wird, kehrt der
Methodenaufruf irgendwann auf die Java Seite zurück. Dann wird dort
der Fehler erneut als Exception geworfen. Die andere Möglichkeit ist auf
der nativen Seite den Fehler zu behandeln. Dann muss mit der Methode
„ExceptionClear()“ aus JNIEnv der Fehler zurückgesetzt werden damit er
auf der Java Seite nicht geworfen wird.
7. Wie verwende ich JNI in meinem Java Programm?
Für die Aufrufe von Java in Richtung nativ gelten spezielle regeln für
externe d.h. nativ (z.B. unter C++) deklarierten Methoden die über Java
aufgerufen werden können. Es muss der voll qualifizierte Methodenname
kodiert werden Paketname + Klassenname + Funktionsname mit
Parametern. Dabei wird der Paketname der sonst mit Punkten getrennt
ist durch Unterstriche getrennt. Zusätzlich zu den Parametern der
Funktion werden zwei weitere Parameter übergeben (weiter oben schon
erleutert). Eine externe (native) Funktion wird in Java dann so deklariert:
package de.paket.jni;
public class Klasse {
public native boolean callNative(int arg0);
}
Auf der nativen Seite muss dann entsprechend der Namenskonvention
für die Methode das passende Gegenstück in einer Headerdatei
deklariert werden:
JNIEXPORT jboolean JNICALL Java_de_paket_jni_Klasse_callNative(JNIEnv*,
jobject, jint);
10
Dieses Verfahren muss für jede auf Java Seite als „native“ deklarierte
Methode angewendet werden. Die JVM sucht in der externen Bibliothek
nach genau dieser Funktion mit genau diesem Namen und exakt dieser
Signatur wenn aus „Klasse“ die Methode „callNative()“ aufgerufen wird.
Jetzt stellt sich die Frage, ob das nicht auch einfacher geht. Bei einem
großen Projekt mit vielen nativen Methoden ist es erstens schwer alle
Konventionsgemäß auf der nativen Seite zu deklarieren. Zweitens
müssen bei Änderungen in Funktionsnamen oder der Parameterliste die
nativen Sourcen nachgepflegt werden. Die gute Nachricht ist: es gibt ein
Kommandozeilentool das dabei hilft die Headerdatei für die native Seite
automatisch zu erzeugen.
Ganz wichtig ist dabei folgende Schritte zu beachten, da sonst die
Headerdatei nicht erzeugt wird! In einer Konsole (cmd unter Windows)
folgende Kommandos der Reihe nach ausführen:
• set JAVA_HOME=C:\Programme\Java\jdkVERSION
• set PATH=%PATH%;%JAVA_HOME%\bin
und schließlich
• C:\JAVA_PROJECT_PATH\bin>javah de.paket.jni.Klasse
(Unter Unix funktioniert das analog)
11
Jetzt wurde in „C:\JAVA_PROJECT_PATH\bin“ eine verwendbare
Headerdatei namens „de_paket_jni_Klasse.h“ erzeugt.
So sieht die automatisch erzeugte Datei dann aus:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class de_paket_jni_Klasse */
#ifndef _Included_de_paket_jni_Klasse
#define _Included_de_paket_jni_Klasse
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:
de_paket_jni_Klasse
* Method:
callNative
* Signature: (I)Z
*/
JNIEXPORT jboolean JNICALL Java_de_paket_jni_Klasse_callNative
(JNIEnv *, jobject, jint);
#ifdef __cplusplus
}
#endif
#endif
Wenn diese Funktion (Java_de_paket_jni_Klasse_callNative(…)) dann
Implementiert und eine .dll daraus erzeugt wurde, kann sie fast schon
von Java Seite aus verwendet werden. Was jetzt noch Fehlt ist das
Laden der externen Ressource über den „loadLibrary“ Befehl. Dafür wird
der Java Code um diese Zeile ergänzt:
package de.paket.jni;
public class Klasse {
static {
System.loadLibrary("LIB_PATH/MeineDynamischeBibliothek");
}
public native boolean callNative(int arg0);
}
12
Unter Windows heißt diese Bibliothek
• MeineDynamischeBibliothek.dll
Unter Unix heißt diese Bibliothek
• libMeineDynamischeBibliothek.so
In beiden Fällen wird sie aber nur über ihren eigentlichen Namen
„MeineDynamischeBibliothek“ geladen. Das Auflösen der Abhängigkeit
des tatsächlichen Namens vom Betriebssystem übernimmt Java intern.
Somit ist der obige Java Code sowohl unter Unix als auch Windows
gleichermaßen gültig.
8. Zusammenfassung
Das JNI bietet die Möglichkeiten sowohl von nativer (C++) Seite aus auf
Java zuzugreifen, als auch umgekehrt von Java aus die native Seite
(C++) einzubinden. Der Einsatz von JNI begegnet jedem indirekt der
Java Programme verwendet oder selbst Java Programme schreibt. Der
Einsatzzweck um von Java aus native Bibliotheken zu verwenden liegt
primär darin die Eigenschaften des Betriebssystems dort direkt nutzbar
zu machen wo das Java Framework keine Bibliotheken anbietet. Oder
auch wo sich native Bibliotheken bereits bewährt haben und jetzt in einer
Java
GUI
eingebettet
werden
sollen.
Nur
die
Steigerung
der
Performance als alleiniger Grund für die Verwendung von JNI ist zwar
ein verbreitetes Einsatzziel, in den meisten Fällen aber nicht mehr weiter
erreichbar. Java selbst ist schon an vielen kritischen Stellen optimiert
und setzt dort intern schon auf native Bibliotheken. Wer von Java Seite
aus JNI in seinen Programmen verwenden möchte, dem Stehen
Werkzeuge zur Verfügung die das Generieren von Headerdateien und
somit den Einsatz erleichtern.
13
9. Quellen
• IBM Understanding the Java Native Interface
o http://publib.boulder.ibm.com/infocenter/javasdk/v6r0/topic/co
m.ibm.java.doc.diagnostics.60/html/jni.html#jni
• Native Methoden und Bibliotheken (1997)
o http://olymp.idle.at/~apollo/books/Java%20in%2021%20Tage
n/kap20.htm
• JNI 1.1 Specification ff.
o http://java.sun.com/j2se/1.4.2/docs/guide/jni/
• Gute Übersicht für den Praktischen Einsatz
o http://www.haertfelder.com/jni.html
14
Herunterladen