Java mit eingebundenen (native) C-Programme Vortrag: Rolf Heitzenröder am 29.6.2000 EDV 1 - Java Native Interface 1 Warum JNI? EDV 1 - Java Native Interface 2 Java bietet eine Vielzahl von Möglichkeiten der Programmierung. Angefangen von graphischen Verschönern bis hin zur Netzwerkund Serverprogrammierung. Doch läßt sich in Zeit kritischen Anwendungen und bei evtl. Plattform spezifischer Programmierung eine Realisierung im normalen Java nicht realisieren. Hier hilft das Java Native Interface (JNI) weiter. EDV 1 - Java Native Interface 3 Mit Hilfe von JNI kann in eine Javaklasse Nativer Code, also ein „Heimischer“ Programmcode eingebunden werden. Damit werden die bereits existierenden Programme (z.B. in C geschriebene) nicht einfach unbrauchbar. Bei der Umstellung auf Java. Auch bei dem vorhergehenden Punkt, einer Zeit kritischen Anwendung, liegt hier noch ein Vorteil in nativen Code. Er ist besser auf das bestehende Betriebssystem zu optimieren. Natürlich wird mit dem einfügen von nativen Code(C-Dateien) die portabilität von Java eingeschränkt. Wie wird nun JNI angewendet? EDV 1 - Java Native Interface 4 Um eine natives Programm nun auch in Java verwenden zu können muß in der Java Klasse eine Methode deklariert werden, bei der Methode wird mit dem native-Schlüsselwort festgelegt, daß es sich um eine Methode handelt, die in einer anderen Programmiersprache geschrieben ist. public native void displayMethode(); EDV 1 - Java Native Interface 5 Ein grober Überblick: 1) Java Klasse mit Methode anfertigen. Diese Klasse deklariert die native Methode. 2) Java Klasse compilieren (javac ...) 3) Mit dieser Class-File wird nun die benötigte Header-Datei erzeugt (javah -jni Class-File) 4) C-Programm schreiben. 5) C-Programm mit der (mit javah) erzeugten Header-Datei compileren in eine (in WIN32) DLL-Datei. Fertig! Native Methode deklarieren: public native void myMethode(); Da der Rumpf der Methode in C geschrieben wird muß die Methode „leer“ bleiben. Sie wird mit einem Semikolon abgeschlossen. EDV 1 - Java Native Interface 6 Bibliothek laden: System.loadLibrary("LibName"); "LibName" ist der Dateiname unter der die DLL compiliert worden ist. Main-Methode: public static void main(String[] args) { new ClassName().myMethode(); } Hier das "alt" bekannte Hello-World-Programm (Java Seite): class HelloWorld { public native void displayHelloWorld(); EDV 1 - Java Native Interface 7 static { System.laodLibrary("hello"); } public static void main(String[] args) { new HelloWorld().displayHelloWorld(); } Dieses Java Programm wird normal compilert. Mit der erzeugten ClassDatei wird nun die erforderliche Header-Datei, für unser C-Programm erzeugt. Dies geschieht mit "javah" und dem Parameter "-jni". javah -jni HelloWorld Zur Information: Die Header-Datei sieht folgendermaßen aus: EDV 1 - Java Native Interface 8 /* DO NOT EDIT THIS FILE - it is machine generated */#include <jni.h> /* Header for class HelloWorld */#ifndef _Included_HelloWorld#define _Included_HelloWorld #ifdef __cplusplusextern "C" {#endif /* * Class: HelloWorld * Method: displayHelloWorld * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld (JNIEnv *, jobject); #ifdef __cplusplus}#endif #endif Diese Datei wird automatisch mit "javah -jni" generiert. Im Javafile wurde hier nur eine native-Methode deklariert. Wird eine weitere benötig und in Java deklariert, ergänzt "javah" die h-Datei ... mit den folgenden Zeilen: EDV 1 - Java Native Interface /* * Class: HelloWorld * Method: deutschHelloWorld * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_zweiteHelloWorld (JNIEnv *, jobject); usw. Diese Funktionen im Header-File besitzen zwei Parameter. JNIEnv* und jobject . 9 "JNIEnv*" ist ein Interface Pointer und "jobjekt" ist ein Parameter der das aktuelle Objekt referenziert. Vergleichbar mit der "this" Variable in Java. In diesem Beispiel werden aber beide Parameter ignoriert. Nun wird es Zeit sich um die Impementierung in C zu kümmern: "javah" hat eine sogenannte Signatur für die Methoden generiert: Java_HelloWorld_displayHelloWorld EDV 1 - Java Native Interface 10 Im C-Code muß nun als Methode/Funktion die gleiche Signatur verwendet werden: Bsp: #include <jni.h> #include "HelloWorld.h" #include <stdio.h> JNIEXPORT void JNICALL Java_HelloWorld_displayHelloWorld( JNIEnv *env, jobject jobj) { printf("Hello world!\n"); return; } "jni.h" stellt informationen bereit, die für das zusammen Spiel mit dem Java Runtime System von Bedeutung ist. Dieses Header-File muß immer im C-Code "included" sein. "hello.h" soll unser generiertes Header-File sein. Nun passiert es auch, daß man Werte den Methoden übergeben muß. Auf Java Seite werden diese Varablen gewöhnlich wie in jeder Methode als Parameter angegeben. Auf C der Seite erhält man die Möglichkeit, alle primitiven Datentypen wie in Java anzugeben, nur mit einem "j" vorangestellt. EDV 1 - Java Native Interface 11 Übersicht der Datentypen: In C haben die primitiven Datentypen den gleichen Namen, nur mit einem j vorangestellt. EDV 1 - Java Native Interface 12 Java C Bits --------------------------------------------boolean jboolean 8 unsi gned byte jbyte 8 char jchar 16, unsigned short jshort 16 int jint 32 long jlong 64 float jfloat 32 double jdouble 64 void void - EDV 1 - Java Native Interface 13 Java-Objekt C-Typ -------------------------------------------------------------Object jobject repräsentiert alle Java-Objekte Class jclass repräsentiert ein Klassenobjekt String jstring jarray repräsentiert ein Java-Array jobjectArray Array von Objekten jbooleanArray Array mit boolean-Werten jbyteArray Array mit byte-Werten jcharArray Array mit char-Werten jshortArray Array mit short-Werten jintArray Array mit int-Werten jlongArray Array mit long-Werten jfloatArray Array mit float-Werten jdoubleArray Array mit double-Werten Throwable jthrowable repräsentiert Java-Exceptions Java Arrays sind keine primitiven Datentypen. Hier gibt es besondere Funktionen für Arrays. EDV 1 - Java Native Interface 14 jarray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement); jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index); void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value); Anstelle von object wird der Datentyp angegeben. Nun ein konkretes Beispiel mit einem vorausgesetzten Quicksortalgorithmus. EDV 1 - Java Native Interface 15 Java-File mit nativer-Methode schreiben: class JavaKlasse { // native Methode public native double[] Jquicksort( double []); // Einmaliger Aufruf der Bibliothek (dll) static { System.loadLibrary("QuickSort"); } // main-Methode public static void main( String args[]) { // Initialisierung vom Vektor double [] vector; ... // Aufruf der nativen Methode vector = new JavaKlasse.Jquicksort(vector); ... // Anzeigen vector } } Generiertes Header-File mit "javah -jni JavaKlasse": EDV 1 - Java Native Interface /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class JavaKlasse */ #ifndef _Included_JavaKlasse#define _Included_JavaKlasse #ifdef __cplusplusextern "C" {#endif /* * Class: JavaKlasse * Method: Jquicksort * Signature: ([D)[D */ JNIEXPORT jdoubleArray JNICALL Java_JavaKlasse_Jquicksort (JNIEnv *, jobject, jdoubleArray); #ifdef __cplusplus} #endif #endif Hier erkennt man die Signatur der Nativen-Methode (Blau dargestellt). In dem folgenden C-File wird diese Methode nun implementiert. 16 #include <stdio.h> #include <jni.h> #include "JavaKlasse.h" JNIEXPORT jdoubleArray JNICALL Java_JavaKlasse_Jquicksort (JNIEnv * env, jobject jobj, jdoubleArray jarr){ EDV 1 - Java Native Interface // Der Vektor muß mit seiner Länge übernommen werden. jsize l = (*env)->GetArrayLength(env, jarr); jdouble *vector = (*env)->GetDoubleArrayElements(env, jarr, 0); /* Aufruf der Methode QuickSort in der Bibliothek QuickSort.dll dieses C-File muß in der gleichen Bilbiothek zur verfügung stehen. */ QuickSort(vector, l) // Speicher freigeben und rückkopieren des Array. (*env)->ReleaseDoubleArrayElements(env, jarr, vector, 0); return jarr; } 17 EDV 1 - Java Native Interface 18 Hier ein paar Anmerkungen: In Java ist die Längeninformation der Arrays im Objekt „Array“ mit inbegriffen. In C ist dies nicht der Fall. So wird vom JNI eine Methode bereitgestellt, die die Länge aus liest. (*env)->GetArrayLength(env, myarray); JNI stellt für die Länge auch ein besondere Variable zur Verfügung: „jsize“ In C wird ein Array mit Pointer (Zeiger) realisiert. Deswegen wird wird das Feld auf ein Pointer gelegt. jdouble *c_feld = (*env)->GetDoubleArrayElements(env, myarray, 0); Um den Speicher der hier in c_feld belegt wurde wieder frei zu machen, wird die Funktion ReleaseDoubleArrayElements()verwendet. Zusätzlich kopiert sie das entsprechende (hier) Array zurück. Quellen: http://java.sun.com/docs/books/tutorial/ http://developer.java.sun.com/developer/onlineTraining/Programming/JDCBook/jni.html http://www.informatik.fh-muenchen.de/~schieder/seminar-java-ss98/jni/JNI.html EDV 1 - Java Native Interface 19 http://www.informatik.uni-osnabrueck.de/bernd/Artikel/jni_1.html http://www.informatik.uni-osnabrueck.de/bernd/Artikel/jni_2.html http://www.tu-chemnitz.de/urz/java/cjug/3/main.html http://fred.ukbf.fu-berlin.de/~hangloos/c_in_java/main.html