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