Java Native Interface Tutorial - Computer

Werbung
JNI
Java Native Interface
Tutorial
JNI Tutorial
1/9
Inhaltsverzeichnis
Revisionsverzeichnis............................................................................................3
Einleitung............................................................................................................4
Erste Schritte.......................................................................................................5
Die Javaklasse..................................................................................................5
JNI Headergenerierung.....................................................................................5
Die C++ DLL....................................................................................................7
Verbindung herstellen......................................................................................8
JNI Tutorial
2/9
Revisionsverzeichnis
Datum
Version
Beschreibung
Autor
27.06.2007
1.0
Init. Release
S. Krümling
JNI Tutorial
3/9
Einleitung
In diesem Tutorial wollen wir uns mit JNI beschäftigen. JNI (Java Native
Interface) ist eine Schnittstelle die es ermöglicht aus Java heraus Native
Bibliotheken (libraries) anzusprechen. Die Bibliotheken können in z.B. in C++
oder in Delphi geschrieben sein. Über Sinn und Unsinn dieser Schnittstelle
bezüglich der Plattformunabhängikeit kann man sich streiten. Dies auch nicht
Zweck dieses Tutorials darüber eine Meinung zu bilden. Fakt ist, dass man,
wenn man weiterhin Plattformunabhängig sein will und JNI benutzen will, für
jede Plattform eine passende Bibliothek mit liefern muss. D.h. unter Windows
wäre das in Form einer DLL (Dynamic Link Library). Unter Linux wäre das eine
lib.so . Ich will aber an der Stelle nicht darüber philosophieren sondern zeigen,
dass es einfach ist diese Schnittstelle zu benutzen und welches Potenzial sie
birgt.
Eins sei noch vorweg gesagt. Diese Schnittstelle ist für Spezialaufgaben
gedacht, die entweder Java nicht gut oder gar nicht lösen kann. Daher
empfehle ich JNI nur zu benutzen wenn es erforderlich wird, da sich sonst die
Frage stellt, ob es noch Sinn macht die Anwendung in Java zu entwickeln.
Jedoch genug derlei Worte.
Ich werde versuchen die Thematik in einfachen Beispielen zu veranschaulichen.
Anders als in einigen anderen Tutorials, werde ich kein großes Beispiel
verwenden was sich durch alle Kapitel zieht, sondern in kleinen Beispielen die
auf die Situation zugeschnitten sind. Ich werde in diesem Tutorial auch nicht
darauf eingehen, wie JNI unter der Haube arbeitet sondern mich darauf
Beschränken wie man es anwendet. Wer dennoch wissen möchte was intern
passiert, dem lege ich die Spezifikationsdokumentation ans Herz.
Technische Voraussetzung ist das Java JDK ab Version 1.4. Ich empfehle jedoch
das JDK 6 zu installieren. Es sollte korrekt installiert und die
Umgebungsvariablen eingerichtet sein. Ich empfehle auch das Java JDK bin
Verzeichnis in die path Variable aufzunehmen. Eventuelle Benutzungshinweise
bezüglich einer IDE werde ich hauptsächlich NetBeans 5.5.1 für Java und
Microsoft Visual Studio 2005 für C++ benutzen. Dieses Vorgehen sollte sich in
den meisten fällen nur geringfügig von anderen IDEs wie z.B. Eclipse
unterscheiden. Ich empfehle jedoch unabhängig davon eine IDE zu benutzen.
Da ich des weiteren an einigen Stelle vorhandene Kenntnisse in Java
voraussetze und nicht weiter drauf eingehe, wenn etwas unklar ist dies im Netz
nach zuschlagen. Das Openbook von Galileocomputing kann ich da empfehlen.
Beispielrelevanter Quellcode ist in gelben Kästchen eingebettet.
JNI Tutorial
4/9
Erste Schritte
Die Schritte die nötig sind um JNI zu benutzen sind folgende:
Die Javaklasse
Hier werden die Methodenrümpfe deklariert, die von Java aus aufgerufen
werden. Unser erstes Beispiel sieht so aus:
public class JniTest {
public static native int getNumber();
}
Was sofort auffällt ist das Schlüsselwort native. Dadurch legen wir fest, dass
Java einen Aufruf über JNI realisieren soll. Solche Methoden haben
grundsätzlich keinen Rumpf. Die Darstellung ähnelt also einem Java typischen
Interface mit dem Unterschied, dass dies eine Klasse und kein Interface ist.
Wäre die Klasse als Interface deklariert wäre ein JNI Aufruf nicht möglich.
Speichern nicht vergessen ;-) .
Jetzt muss die Javaklasse kompiliert werden. Dies kann man selbst in der
Kommandozeile tun oder wenn man sich eine IDE (Eclipse, NetBeans) installiert
hat darüber tun. In NetBeans kann man im Projektfenster auf die Klasse rechts
klicken und „Compile File“ auswählen. Alternativ dazu kann man auch die F9
Taste drücken. Auf der Kommandozeile sieht der Aufruf wie folgt aus:
Zuerst bewegend wir uns in den Ordner wo sich die Javaklasse befindet. Dann
führen wir den Javacompiler aus mit:
javac JniTest.java
wenn dies Fehlerfrei ausgeführt wurde müsste sich jetzt in dem Verzeichnis wo
die JniTest.java liegt eine JniTest.class befinden. Dies ist unsere compilierte
Javaklasse.
JNI Headergenerierung
Als nächstes müssen wir uns einen C++ header generieren lassen, damit wir in
unserer C++ DLL den JNI Aufruf entgegennehmen können. Dies geschieht
ausschließlich auf der Kommandozeile. Wie begeben uns auf der
Kommandozeile wieder in den Ordner wo sich die java und die class Datei
befinden. Dann führen wir folgenden Befehl aus um den Header zu generieren:
javah -o cpp_interface.h JniTest
Erfolgt dies wieder ohne Fehler müsste sich in dem Ordner jetzt zusätzlich eine
Datei cpp_interface.h befinden. Dieser Name des Headers ist frei wählbar.
JNI Tutorial
5/9
Hinweis :
Die Generierung des Headers kann nur über die kompilierte Javaklasse
erfolgen. Generierungsversuche auf die .java Datei hat die Meldung „could
not found“ zufolge.
Sehen wir uns den generierten Header genauer an. Mit ganzen drumherum
filtern wir uns mal eine wichtige Stelle raus. Die Methodendeklaration auf C
Seite.
JNIEXPORT jint JNICALL Java_JniTest_getNumber
(JNIEnv *, jclass);
Die Methodendeklaration wirkt auf den ersten Blick etwas Kryptisch aber es ist
halb so schlimm wie es aussieht. Nehmen wir die Deklaration auseinander.
jint spezifiziert den Rückgabetyp auf C Seite. Es ist ein von JNI zur Verfügung
gestellter äquivalenter Typ zum Javadatentyp.
Der Methodenname setzt sich wie folgt zusammen. Jede JNI Methode beginnt
mit Java_ gefolgt vom voll qualifizierten Klassennamen (D.h. mit kompletter
Paketangabe) und dem Namen der Methode die wir in Java deklariert haben.
Die Trennungspunkte des Paketpfades wurden durch Unterstriche erstetzt. D.h.
würde unter Java unsere Klasse den Paketpfad de.computerlabs unterliegen,
würde der voll qualifizierte Klassenname de.computerlabs.JniTest lauten
und JNI würde diese Methodendeklaration erstellen:
JNIEXPORT jint JNICALL Java_de_computerlabs_JniTest_getNumber
(JNIEnv *, jclass);
Wie man gut erkennen kann wurde der komplette Paketpfad im Namen
verwendet. In diesem Tutorial verzichten wir aber auf einen Paketpfad.
Wichtig!
Wenn man den Paketpfad nachträglich ändert zieht das weitere
Änderungen nach sich. Java löst den Paketpfad auf dem Dateisystem als
Ordnerstruktur auf. D.h. bei dem Paketpfad de.computerlabs würde die
.java datei im Quellenverzeichnis unter de/computerlabs/JniTest.java
liegen. Auch der Javah Aufruf zum generieren des Headers ändert sich
etwas. Statt vorher javah -o interface.h JniTest würde dieser nun so
lauten javah -o interface.h de.computerlabs.JniTest
JNI Tutorial
6/9
Die C++ DLL
Jetzt machen wir uns daran den C++ Teil zu erstellen.
Ich gehe an der Stelle jetzt nicht darauf ein, wie man die DLL erstellt. D.h. ich
gehe davon aus das eine geeignete Umgebung vorhanden ist um eine C++
DLL zu erstellen, sei dies MS Visual Studio oder über ein C/C++ Modul für
NetBeans oder Eclipse.
Wichtig ist an der Stelle, dass (bei IDEs unter den Projekteinstellungen)
zusätzliche Include-Verzeichnisse (additional Include directories) eingetragen
werden müssen. Im Java JDK Verzeichnis sind das include und include/win32 die
hinzugefügt werden müssen. Dort befinden sich Headerdateien, u.a. jni.h, die
Klassen und Strukturen bereitstellen um auf der C Seite mit Java
kommunizieren zu können.
Als nächstes binden wir den von JNI erstellten Header ein. Ist das getan,
erstellen wir eine neue c++ Datei. Dort wollen wir die Methode
implementieren. Wir benötigen dazu 2 zusätzliche includes. Einmal die JNI.h
und unsere interface.h. Den Methodenrumpf kopieren wir fast 1 zu 1 aus dem
Header. Nur entfernen wir das Semikolon und schreiben stattdessen die
geschweiften Klammern {}. Als einzige Anweisung geben wir einfach eine 10
zurück. Damit müsste es jetzt wie folgt in der C++ Datei aussehen.
#include "stdafx.h"
#include <jni.h>
#include "interface .h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
}
return TRUE;
JNIEXPORT jint JNICALL Java_JniTest_getNumber
(JNIEnv * pEnv, jclass clazz)
{
return 10;
}
Damit ist es jetzt auch schon getan. Jetzt muss die DLL nur noch gebaut
werden. Ist das getan müssen wir die DLL jetzt in Java einbinden, damit Java sie
aufrufen kann. Wir benennen die DLL nachträglich in JNITestNative.DLL um,
damit im folgenden keine Missverständnisse bei der Bezeichnung entstehen.
JNI Tutorial
7/9
Verbindung herstellen
Nachdem wir die DLL erstellt haben, müssen wir sie einbinden, damit Java die
Methodenimplementierung findet und ausführen kann. Das erfolgt über den
Befehl System.loadLibrary . Dieser sollte nur einmal ausgeführt werden im
Programm. Wenn man versucht mehrmals eine DLL zu laden, wirft Java einen
Fehler. Um das zu verhindern binden wir die DLL statisch ein. Das sieht im
Quelltext so aus.
static {
System.loadLibrary("JNITestNative");
}
Und damit sähe die Komplette Javaklasse so aus.
public class JniTest {
static {
System.loadLibrary("JNITestNative");
}
public static native int getNumber();
}
So stellen wir sicher, dass die DLL nur einmal geladen wird und dies geschieht
sobald die klasse instantiiert wird. Auch wenn wir jetzt die Javaklasse
nachträglich im Inhalt geändert haben, müssen wir nicht das C++ Interface
neu erstellen, da das nur nötig ist, wenn eine native Anweisung hinzukommt
oder wegfällt. Jedoch müssen wir die Javaklasse selbst noch mal neu
kompilieren. Wieder entweder mit rechts klicken auf die Klasse im
Projektfenster oder über javac.
Im Normalfall muss man die DLL in den classpath des Java Programms
aufnehmen, damit Java diese findet. Aber um zu demonstrieren, wie JNI
grundsätzlich funktioniert, reicht es die DLL ins Windows/System32 Verzeichnis
zu kopieren. System.loadLibrary sucht standardmäßig u.a. dort nach DLLs.
Ist das getan, können wir eine main() Methode in unsere Klasse
implementieren. Dort rufen wir einfach die statische Methode getNumber()
auf.
JNI Tutorial
8/9
public static void main(String[] args) {
System.out.println(getNumber());
}
Damit sehe die Klasse wie folgt fertig aus.
public class JniTest {
static {
System.loadLibrary("JNITestNative");
}
public static native int getNumber();
public static void main(String[] args) {
System.out.println(getNumber());
}
}
Klasse kompilieren und dann können wir sie auch schon ausführen. Dies
geschieht mit folgedem Befehl.
java JniTest
Und als Ergebnis sollte dann 10 kommen.
Damit beenden wir das erste Kapitel.
JNI Tutorial
9/9
Herunterladen