460 likes | 643 Views
Das Java Native Interface. Wozu JNI?. Man benötigt Plattform-spezifische Features, die nicht durch die vorhandenen JAVA-Klassen bereitgestellt werden. Z.B.: Zugriff auf einen Bandroboter über die SCSI-Schnittstelle um eine grafische Oberfläche zur Bedienung des Roboters zu entwickeln.
E N D
Das Java Native Interface EDV2 - 02 - JavaNativeInterface
Wozu JNI? • Man benötigt Plattform-spezifische Features, die nicht durch die vorhandenen JAVA-Klassen bereitgestellt werden. Z.B.: • Zugriff auf einen Bandroboter über die SCSI-Schnittstelle um eine grafische Oberfläche zur Bedienung des Roboters zu entwickeln. • Es ist eine Bibliothek mit Routinen vorhanden, die nicht in JAVA programmiert wurden. Z.B.: • Ausnutzung einer speziellen Hardware für numerische Berechnungen. • Ein kleiner zeitkritischer Programmabschnitt soll in einer hardwarenahen Sprache programmiert werden, um das Programm zu beschleunigen. Z.B.: • Textsuche in einem Editor • Skalarprodukt in einem numerischen Programm • Dreiecksberechnung in einem FEM-Code EDV2 - 02 - JavaNativeInterface
Was bietet JNI? • Erzeugen, Analysieren und Verändern von JAVA-Objekten (einschließlich Felder und Zeichenketten) in C-Programmen. • Aufruf von JAVA-Methoden in C-Programmen. • Erzeugen und Abfangen von JAVA-Ausnahmen in C-Programmen. • Laden von JAVA-Klassen in C-Programmen. • Konsequente Typprüfung. EDV2 - 02 - JavaNativeInterface
Aufruf von C-Routinen aus JAVA • Das Java Native Interface ermöglicht die Verbindung zwischen Programmen, die in JAVA geschrieben wurden mit Programmen, die in anderen Sprachen geschrieben sind, z.B. C, C++, Assembler, FORTRAN u.s.w. • Ziele: • Die strenge Schnittstellenprüfung von JAVA soll erhalten bleiben. • Es soll ermöglicht werden aus den anderen Sprachen heraus • JAVA-Objekte zu lesen und zu erzeugen, • JAVA-Methoden aufzurufen und • JAVA-Ausnahmen zu erzeugen. • Probleme: • Unterschiede bei den primitiven Datentypen • Unterschiede bei Objekttypen • Unterschiede bei der Parameterübergabe • Es werden eine Reihe von Tools zu Verfügung gestellt, die die Kopplung zwischen JAVA und C bzw. C++ vereinfachen. EDV2 - 02 - JavaNativeInterface
HelloWorld: aufzurufendes C-Programm #include <stdio.h> void helloWorld() { printf("Hello, world!\n"); } Das Programm helloWorld sei vorgegeben und als Header-File hello.h sowie als Objektlibrary (LIB) hello.lib gespeichert. Problem: Wie kann helloWorld in einem JAVA-Programm aufgerufen werden. Lösung: Entwickeln einer Schnittstelle, die in einer oder mehreren JAVA-Klassen realisiert ist und den komfortablen Zugriff auf die Library ermöglicht. EDV2 - 02 - JavaNativeInterface
Struktur HelloWorld.class Hello.class C_hello.h C_hello.c C_hello.dll hello.h hello.lib HelloWorld.java Hello.java EDV2 - 02 - JavaNativeInterface hello.c
Aufrufendes JAVA-Programm public class Hello { public static native void helloWorld(); static { System.loadLibrary("C_hello"); } } EDV2 - 02 - JavaNativeInterface
Das Tool javah • Das Tool javah dient dazu aus einem JAVA-Programm, das native-Methoden enthält, das header-File zu erzeugen, das die Schnittstelle zum Interface-Programm beschreibt. • Aufruf: javah –o h-file.h filename • Es wird ein header-File filename.h erzeugt, das die zu realisierenden Schnittstellen beschreibt. • Beispiel: javah -o C_hello.h Hello erzeugt C_hello.h EDV2 - 02 - JavaNativeInterface
Erzeugtes header-File C_Hello.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class Hello */ #ifndef _Included_Hello #define _Included_Hello #ifdef __cplusplus extern "C" { #endif /* * Class: Hello * Method: helloWorld * Signature: ()V */ JNIEXPORT void JNICALL Java_Hello_helloWorld (JNIEnv *, jclass); #ifdef __cplusplus } #endif #endif EDV2 - 02 - JavaNativeInterface
Ausfüllen der Schnittstelle • Es ist nun das Interface zu programmieren. • Dazu muss das in dem Header-File definierte Programm Java_Hello_helloWorldprogrammiert werden. • Die Parameter können zunächst vernachlässigt werden. #include "C_hello.h" /* * Class: Hello * Method: helloWorld * Signature: ()V */ JNIEXPORT void JNICALL Java_Hello_helloWorld (JNIEnv * env, jclass class) { helloWorld(); } EDV2 - 02 - JavaNativeInterface
Übersetzen des C-Interfaces • Bei der Übersetzung muss die Library hello.dll eingebunden werden:hello.lib • Es müssen die JNI-spezifischen Include-Verzeichnisse angegeben werden:-Ic:\jdk1.3\include -Ic:\jdk1.3\include\win32 • Es muss eine DLL erzeugt werden:-LD -FeC_hello.dll • Insgesamt für MS-C-Compiler unter Windows:cl -Ic:\jdk1.3\include -Ic:\jdk1.3\include\win32 C_hello.c hello.lib-LD -FeC_hello.dll EDV2 - 02 - JavaNativeInterface
Nutzung des Interfaces • Das so entwickelte Interface kann nun wie eine ganz normale JAVA-Klasse verwendet werden. public class HelloWorld { public static void main(String[] args) { Hello.helloWorld(); } } EDV2 - 02 - JavaNativeInterface
Übergabe von Parametern • Problem: Datentypen von JAVA und C sind nicht kompatibel. • C-Datentypen sind nicht vollständig standardisiert und damit abhängig von Compiler, Betriebssystem und Hardware. • JAVA-Datentypen sind vollständig standardisiert und überall identisch. • Der Zugriff auf Attribute von Parameter-Objekten ist naturgemäß recht kompliziert. EDV2 - 02 - JavaNativeInterface
Beispiel: EchoText #include <stdio.h> void helloWorld() { printf("Hello, world!\n"); } void echoText(const char *text) { printf("%s\n",text); } EDV2 - 02 - JavaNativeInterface
Header-File:C_hello.h • Zusätzlich wird folgender Text generiert. • Er enthält einen zusätzlichen Parameter vom Typ jstring. • Problem: dieser Parameter muss in eine Zeichenkette vom C-Typ char* umgewandelt werden. /* * Class: Hello * Method: echoText * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_Hello_echoText (JNIEnv *, jclass, jstring); EDV2 - 02 - JavaNativeInterface
Lesen des Parameters • Problem: Umwandlung des JAVA-String-Parameters in ein entsprechendes C-Format (const char *). • JNI stellt dafür diverse Methoden zur Verfügung. • Die Methoden sind erreichbar • in C: (*env)->Funktionsname(env,parameter); • In C++: env->Funktionsname(parameter); • Speziell lesen einer Unicode-Zeichenkette:GetStringUTFChars(env, text, isCopy) • Alle mit (*env)->GetXXXX angelegten C-Objekte müssen mit (*env)->ReleaseXXXX wieder freigegeben werden, wenn sie nicht mehr benötigt werde. EDV2 - 02 - JavaNativeInterface
Beispiel JNIEXPORT void JNICALL Java_Hello_echoText (JNIEnv *env, jclass class, jstring text) { const char *ctext; ctext= (*env)->GetStringUTFChars(env, text, NULL); echoText(ctext); (*env)->ReleaseStringUTFChars(env, text, ctext); } EDV2 - 02 - JavaNativeInterface
Erzeugen von JAVA-Strings • Zum Erzeugen eines JAVA-Strings aus einer C-Zeichenkette gibt es die Methodejstring NewStringUTF(JNIEnv *env, const char *bytes); • Achtung: Nicht vergessen, die im C-Teil reservierten Speicher freizugeben. EDV2 - 02 - JavaNativeInterface
Beispiel: concatSrings char *concatStrings(const char *s1, const char *s2) { char *s; s=(char *)malloc(strlen(s1)+strlen(s2)+1,sizeof(s[0])); strcpy(s,s1); strcat(s,s2); return s; } EDV2 - 02 - JavaNativeInterface
Hello.java public class Hello { public static native void helloWorld(); public static native void echoText(String text); public static native String concatStrings(String s1, String s2); static { System.loadLibrary("C_hello"); } } EDV2 - 02 - JavaNativeInterface
C_hello.h /* * Class: Hello * Method: concatStrings * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_Hello_concatStrings (JNIEnv *, jclass, jstring, jstring); EDV2 - 02 - JavaNativeInterface
C_hello.c /* * Class: Hello * Method: concatStrings * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_Hello_concatStrings (JNIEnv *env, jclass class, jstring s1, jstring s2) { const char *cs1; const char *cs2; char *cs; jstring s; cs1= (*env)->GetStringUTFChars(env, s1, NULL); cs2= (*env)->GetStringUTFChars(env, s2, NULL); cs = concatStrings(cs1, cs2); (*env)->ReleaseStringUTFChars(env, s1, cs1); (*env)->ReleaseStringUTFChars(env, s2, cs2); s=(*env)->NewStringUTF(env, cs); free(cs); return s; } EDV2 - 02 - JavaNativeInterface
Zugriffsmethoden für Zeichenketten • C-ASCII-Zeichenkette JAVA-Zeichenkette • jstring NewStringUTF(JNIEnv *env, const char *bytes) • const char* GetStringUTFChars(JNIEnv *env, jstring string, jboolean *isCopy) • void ReleaseStringUTFChars(JNIEnv *env, jstring string, const char *utf) • jsize GetStringUTFLength(JNIEnv *env, jstring string) • C-Unicode-Zeichenkette JAVA-Zeichenkette • jstring NewString (JNIEnv *env, const jchar *bytes) • const jchar* GetStringChars(JNIEnv *env, jstring string, jboolean *isCopy) • void ReleaseStringChars(JNIEnv *env, jstring string, const jchar *utf) • jsize GetStringLength(JNIEnv *env, jstring string) EDV2 - 02 - JavaNativeInterface
Zugriff auf Felder • Wie für Zeichenketten gibt es für alle Arten von eindimensionalen Feldern entsprechende New-, Get-, Set- und Release-Methoden. • Dabei muss unterschieden werden zwischen den Zugriffsmethoden für Felder von primitiven Typen und Felder von Objekten. • jsize GetArrayLength(JNIEnv *env, jarray array)bestimmt die Länge des Feldes (array.length). • <ArrayType> New<PrimitiveType>Array(JNIEnv *env, jsize length) bzw.jarray NewObjectArray(JNIEnv *env, jsize length, jclass elementClass, jobject initialElement)erzeugt ein neues Feld EDV2 - 02 - JavaNativeInterface
Felder umkopieren • <NativeType> *Get<PrimitiveType>ArrayElements (JNIEnv *env, <ArrayType> array, jboolean *isCopy)erzeugt ein C-Feld und kopiert ggf. den Inhalt des JAVA-Feldes in das C-Feld. Änderungen des C-Feldes werden ggf. erst bei dem entsprechenden Release-Aufruf zurückkopiert. isCopy liefert JNI_TRUE, wenn das Feld kopiert wurde, JNI_FALSE sonst. • void Release<PrimitiveType>ArrayElements(JNIEnv *env, <ArrayType> array, <NativeType> *elems, jint mode)das C-Feld wird ggf. auf das JAVA-Feld zurückkopiert und die Ressourcen werden wieder freigegeben.mode steuert die Funktionen: • 0 : Kopieren der Daten und Ressourcen freigeben • JNI_COMMIT : Kopieren der Daten und Ressourcen nicht freigeben • JNI_ABORT: Ressourcen freigeben und Daten nicht kopieren EDV2 - 02 - JavaNativeInterface
Teile von Feldern umkopieren • void Get<PrimitiveType>ArrayRegion(JNIEnv *env, <ArrayType> array, jsize start, jsize len,<NativeType> *buf)kopiert die Elemente start,...,start+len-1 aus dem JAVA-Feld array in das C-Feld buf • void Set<PrimitiveType>ArrayRegion(JNIEnv *env, <ArrayType> array, jsize start, jsize len,<NativeType> *buf)kopiert die Elemente start,...,start+len-1 aus dem C-Feld buf in das JAVA-Feld array EDV2 - 02 - JavaNativeInterface
Elemente von Object-Arrays • jobject GetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index)liest ein Element aus eine Object-Array • void SetObjectArrayElement(JNIEnv *env, jobjectArray array, jsize index, jobject value)schreibt das Element value in das Feld EDV2 - 02 - JavaNativeInterface
JAVA-Typen / C-Typen EDV2 - 02 - JavaNativeInterface
Beispiel : matrixXvector void matrixXvector(int n, int m, double *matrix, double *vector, double *result) { int i,j; for (i=0;i<m;i++) { result[i]=0.0; for (j=0;j<n;j++) { result[i]+=matrix[i*n+j]*vector[j]; } } } EDV2 - 02 - JavaNativeInterface
C_hello.h /* * Class: Hello * Method: matrixXvector * Signature: ([[D[D)[D */ JNIEXPORT jdoubleArray JNICALL Java_Hello_matrixXvector (JNIEnv *, jclass, jobjectArray, jdoubleArray); EDV2 - 02 - JavaNativeInterface
C-hello.c JNIEXPORT jdoubleArray JNICALL Java_Hello_matrixXvector (JNIEnv *env, jclass clazz, jobjectArray matrix, jdoubleArray vector) { jsize n, m, i; jdouble *cMatrix; jdouble *cVector; jdouble *cResult; jboolean isCopy; jdoubleArray zeile; jdoubleArray result; n=(*env)->GetArrayLength(env, vector); m=(*env)->GetArrayLength(env, matrix); cVector=(*env)->GetDoubleArrayElements(env, vector, &isCopy); cMatrix=(jdouble *)calloc(m*n,sizeof(jdouble)); for (i=0;i<m;i++){ zeile=(jdoubleArray)((*env)->GetObjectArrayElement(env, matrix, i)); (*env)->GetDoubleArrayRegion(env, zeile, 0, n, &(cMatrix[i*n])); } cResult=(jdouble *)calloc(m,sizeof(jdouble)); matrixXvector(n, m, cMatrix, cVector, cResult); result=(*env)->NewDoubleArray(env, m); (*env)->SetDoubleArrayRegion(env, result, 0, m, cResult); free(cMatrix); free(cResult); (*env)->ReleaseDoubleArrayElements(env, vector, cVector, JNI_ABORT); return result; } EDV2 - 02 - JavaNativeInterface
Zugriff auf JAVA-Objekte • Objekte können als Parameter an das Interface JAVA-C übergeben werden und es soll auf die Attribute und Methoden des Objektes zugegriffen werden. • Es soll auf JAVA-Klassen und deren statische Methoden zugegriffen werden, z.B. sollen die Methoden von StrictMath im C-Programm verwendet werden. • Es sollen im C-Programm Objekte erzeugt werden, z.B. um ein Objekt als return-Wert zurückgeben zu können. • Für die Unterscheidung der überladenenMethode spielen die Signaturen eine entscheidende Rolle. • Mit Hilfe der Signaturen lassen sich Datentypen eindeutig und relativ kompakt beschreiben. EDV2 - 02 - JavaNativeInterface
Signaturen EDV2 - 02 - JavaNativeInterface
Beispiele für Signaturen • Signatur von Feldern: Für jeden Index eine "[" anschließend die Signatur des Elementtyps.Z.B.: double[][] [[D • Signatur von Objekten: Lvoller-Klassen-Name;Z.B.: String Ljava/lang/String; • Signatur von Methoden: (Signaturen der Parameter)Signatur des Wertes.Z.B.: double[] methode(int[][], boolean)([[DZ)[D EDV2 - 02 - JavaNativeInterface
Zugriff auf Attribute von Objekten • Allgemeiner Typ von Objekten: jobject • Der Zugriff auf Attribute erfolgt über den FieldID. Um den FieldID zu bestimmen benötigt man: • Die Klasse zu der das Objekt gehört. • Den Namen des Attributes. • Die Signatur des Attributes. • Es muss unterschieden werden zwischen statischen und nichtstatischen Attributen und Methoden. • jclass GetObjectClass(JNIEnv *env, jobject obj)bestimmt die Klasse zu der das Objektobjgehört. • jclass FindClass(JNIEnv *env, const char *name)bestimmt die Klasse anhand ihres vollständigen Namens • Für interne Klassen gilt der Klassenname: Klasse$interneKlasse EDV2 - 02 - JavaNativeInterface
nichtstatische Attribute • jfieldID GetFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)bestimmt aus der Klasse, dem Attributnamen und seiner Signatur den FieldID. • <NativeType> Get<PrimitiveType>Field(JNIEnv *env, jobject obj, jfieldID fieldID)liest den Wert des Attributes, funktioniert auch für Objekt-Typen • void Set<type>Field(JNIEnv *env, jobject obj, jfieldID fieldID, <NativeType> value)schreibt einen Wert in das Attribut EDV2 - 02 - JavaNativeInterface
Beispiel ... method(JNIEnv *env, jclass class, jobject obj) { jclass objClass; jfieldID attrID; jint intAttr; objClass=(*env)->GetObjectClass(env, obj); attrID=(*env)->GetFieldID(env, class, "dimension", "I"); intAttr=(*env)->GetIntField(env, obj, attrID); intAttr=2*intAttr+77; (*env)->SetIntField(env, obj, attrID, intAttr); } EDV2 - 02 - JavaNativeInterface
statische Attribute • jfieldID GetStaticFieldID(JNIEnv *env, jclass clazz, const char *name, const char *sig)bestimmt aus der Klasse, dem Attributnamen und seiner Signatur den FieldID. • <NativeType> GetStatic<PrimitiveType>Field(JNIEnv *env, jclass clazz, jfieldID fieldID)liest den Wert des Attributes, funktioniert auch für Objekt-Typen • void SetStatic<type>Field(JNIEnv *env, jclass clazz, jfieldID fieldID, <NativeType> value)schreibt einen Wert in das Attribut EDV2 - 02 - JavaNativeInterface
Beispiel ... method(JNIEnv *env, jclass class, jobject obj) { jclass objClass; jfieldID attrID; jint intAttr; objClass=(*env)->GetObjectClass(env, obj); attrID=(*env)->GetStaticFieldID(env, class, "dimension", "I"); intAttr=(*env)->GetStaticIntField(env, class, attrID); intAttr=2*intAttr+77; (*env)->SetStaticIntField(env, class, attrID, intAttr); } EDV2 - 02 - JavaNativeInterface
nichtstatische Methoden von Objekten • jmethodID GetMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig)bestimmt den MethodID einer Methode. Bei überladenen Methoden wird die sie durch die Signatur identifiziert. • Diese Varianten des Aufrufs von JAVA-Methoden unterscheiden sich nur in der Art der Übergabe der Parameter. • <NativeType> Call<PrimitiveType>Method(JNIEnv *env, jobject obj, jmethodID methodID, ...)alle Parameter werden nacheinander angegeben • <NativeType> Call<PrimitiveType>MethodA(JNIEnv *env, jobject obj, jmethodID methodID, jvalue *args)die Parameter werden als Feld übergeben • <NativeType> Call<PrimitiveType>MethodV(JNIEnv *env, jobject obj, jmethodID methodID, va_list args)die Parameter werden als Liste übergeben EDV2 - 02 - JavaNativeInterface
Beispiel method(JNIEnv *env, jclass class, jobject obj) { jclass objClass; jmethodID methID; objClass=(*env)->GetObjectClass(env, obj); methID=(*env)->GetMethodID(env, objClass, "setDimension", "(I)V"); (*env)->CallVoidMethod(env, obj, methID, 777); } EDV2 - 02 - JavaNativeInterface
Aufruf statischer Methoden • jmethodID GetStaticMethodID(JNIEnv *env, jclass clazz, const char *name, const char *sig) • <NativeType> CallStatic<PrimitiveType>Method (JNIEnv *env, jclass clazz, jmethodID methodID, ...)alle Parameter werden nacheinander angegeben • <NativeType> CallStatic<PrimitiveType>MethodA (JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args)die Parameter werden als Feld übergeben • <NativeType> CallStatic<PrimitiveType>MethodV (JNIEnv *env, jclass clazz, jmethodID methodID, va_list args)die Parameter werden als Liste übergeben EDV2 - 02 - JavaNativeInterface
Beispiel ... jdouble sin(JNIEnv *env, jclass clazz, jdouble x) { jclass cl; jmethodID sinID; jdouble sx; cl=(*env)->FindClass(env, "java/lang/StrictMath"); sinID=(*env)->GetStaticMethodID(env, cl, "sin", "(D)D"); sx=(*env)->CallStaticDoubleMethod(env, cl, sinID, x); return sx; } EDV2 - 02 - JavaNativeInterface
Neue JAVA-Objekte erzeugen • jobject AllocObject(JNIEnv *env, jclass clazz)erzeugt eine neues Objekt der Klasse clazz ohne einen Konstruktor aufzurufen • jobject NewObject(JNIEnv *env, jclass clazz, jmethodID methodID, ...) • jobject NewObjectA(JNIEnv *env, jclass clazz, jmethodID methodID, jvalue *args) • jobject NewObjectV(JNIEnv *env, jclass clazz, jmethodID methodID, va_list args)erzeugt ein neues Objekt, wobei der Konstruktor mit der ID methodID und den angegebenen Parametern benutzt wird. • Als Name des Konstruktors wird "<init>" verwendet. Als Signatur für den return-Wert "V". • Bei internen Klassen ist die Parameterliste am Anfang um einen Parameter jclass zu erweitern. Diesem ist der Parameter class der C-Methode zu übergeben. EDV2 - 02 - JavaNativeInterface
Beispiel ...jobject method(JNIEnv *env, jclass clazz) { jclass tsClass; jmethodID tsConst; jobject tsObj; jmethodID tsAdd; tsClass=(*env)->FindClass(env, "java/util/TreeSet"); tsConst=(*env)->GetMethodID(env, tsClass, "<init>", "()V"); tsObj=(*env)->NewObject(env, tsClass, tsConst); tsAdd=(*env)->GetMethodID(env, tsClass, "add", "(Ljava/lang/Object;)Z"); (*env)->CallBooleanMethod(env, tsObj, tsAdd, (*env)->NewStringUTF(env, "Mueller")); (*env)->CallBooleanMethod(env, tsObj, tsAdd, (*env)->NewStringUTF(env, "Meier")); return tsObj; } EDV2 - 02 - JavaNativeInterface
Das Tool javap • Mit javap kann man class-Files bearbeiten. Javap liefert: • Ausgabe des Byte-Codes (-c) • Tabelle der lokalen Variablen (-l) • Signaturen alle Methoden (-s) EDV2 - 02 - JavaNativeInterface