310 likes | 529 Views
Java Native Interface. Eine Einführung. Motivation. Java: abstrahiert von Hardware und Betriebssystem + hohe Portabilität + hohe Sicherheit ( Sandbox -Prinzip) - verminderte Performanz (mit heutigen JIT-Compilern i.A. kein Problem mehr)
E N D
Java Native Interface Eine Einführung
Motivation • Java: abstrahiert von Hardware und Betriebssystem + hohe Portabilität + hohe Sicherheit (Sandbox-Prinzip) - verminderte Performanz (mit heutigen JIT-Compilern i.A. kein Problem mehr) - kein direkter Zugriff auf Hardware-/Betriebssystem • C/C++: Programmiersprache für OS-Entwicklung (UNIX) + maschinennah, hohe Performanz + alt, weit verbreitet, umfangreiche Sammlung von Legacy-Code - Programmierung möglicherweise umständlich (GUI in C?) - hohe Anforderungen an Programmierer
Das Java Native Interface (JNI) • Schnittstelle, um C/C++ -Code in Java einzubinden und umgekehrt • Beispiele - C/C++ in Java: • Treiber, Betriebssystemfunktionen ansprechen (SWT) • Vorhandene Bibliotheken einbinden • Zeitkritische Funktionen in C oder Assembler einbinden Möglicherweise Verlust der Portabilität! • Beispiel – Java in C/C++: • Browser, der Applets ausführt
JNI - Architektur • Schnittstelle definiert Datentypen und Funktionen… • zum Aufruf nativer Funktionen in Java • zur Abbildung von Java-Datentypen in C/C++ • zur Erzeugung und Mani- pulation von Java-Objekten in C/C++ (OO in C?) • zum Aufruf einer JVM aus C/C++ heraus
C in Java : Hallo Welt • Minimal-Beispiel: Aufruf einer nativen Methode, die „Hallo Welt!“ auf der Konsole ausgibt. • Prinzipielles Vorgehen (immer gleich):
Hallo Welt – Java-Code • Definition einer nativen Methode ohne Rumpf • Dynamisches Laden der Bibliothek, die Methode bereitstellt • Kompilieren mit: javac HelloWorld.java classHelloWorld { private nativevoidprint(); public static void main(String[] args) { newHelloWorld().print(); } static { System.loadLibrary("HelloWorld"); } }
Hallo Welt – C/C++-Header • Die C-Methode, die in HelloWorld aufgerufen wird, muss gewisse Konventionen erfüllen • javah –jniHelloWorld.classerzeugt Header-Datei HelloWorld.hmit Prototypen aller nativen Methoden: • Methoden-Name muss in C eindeutig sein, daher: Java_packageName_className_methodName • Parameterlose Funktion print() erhält in C zwei Parameter !? • include <jni.h> […] • JNIEXPORT void JNICALL • Java_HelloWorld_print (JNIEnv *env, jobject); […]
Hallo Welt – C-Code • Implementierung der in Header-Datei definierten Funktion in Datei HelloWorld.c: • Übersetzen in gemeinsam genutzte Bibliothek gcc -fPIC -c HelloWorld.c ld -shared -sonamelibHelloWorld.so.l -o libHelloWorld.soHelloWorld.o • #include "HelloWorld.h" • #include <stdio.h> • JNIEXPORT void JNICALL • Java_HelloWorld_print (JNIEnv *env, jobjectobj){ • printf("Hallo Welt!\n"); • }
Hallo Welt - Finale • Ausführen des Java-Programms: • javaHelloWorld • java.lang.UnsatisfiedLinkError: no HelloWorld in library path at java.lang.Runtime.loadLibrary(Runtime.java) at java.lang.System.loadLibrary(System.java) at HelloWorld.main(HelloWorld.java) Pfad zur Bibliothek noch unbekannt! Entweder • Bibliothek in /usr/lib verschieben (root-Rechte!), • setenv LD_LIBRARY_PATH .:${LD_LIBRARY_PATH}, • oder Bibliothekpfad explizit an Java übergeben: java –Djava.library.path=. HelloWorld
Abbildung der Datentypen • Bisher: kein Datenaustausch zwischen Java und C • Datentypen in Java: Primitive Datentypen, Klassen, Objekte, Arrays • Unterschiede Java vs. C: • Java: Einheitliche Spezifikation primitiver Datentypen auf allen Plattformen C: plattformabhängig (z.B. 2Byte int, 4Byte int) • Java: Objektorientierung C: keine Objekte, aber strukturierte Datentypen und Funktionszeiger
Abb. primitiver Datentypen – Beispiel • publicclassSum{ • static{ System.loadLibrary("Sum"); } • publicstatic native intsum(int a, int b); • publicstaticvoidmain(String[] args){ • System.out.println("sum(3,4)=" + sum(3,4)); • }} #include <jni.h> […] JNIEXPORT jint JNICALL Java_Sum_sum (JNIEnv *env, jclass, jint, jint); […] • JNIEXPORT jint JNICALL • Java_Sum_sum(JNIEnv *env, jclass cl, jint a, jint b){ • jintresult = a + b; • returnresult; • }
Abbildung von Referenz-Datentypen • Objekte, Klassen und Arrays werden in Java über Referenzen angesprochen • An C wird ein „transparenter“ Pointer übergeben, der auf die Objekte in der JVM zeigt. • Zugriff auf die Objekte erfolgt immer über die JNIEnv-Umgebung, die jeder nativen Methode übergeben wird:
Abb. von Referenztypen – z.B. Arrays importjava.util.Random; publicclassSort{ static{ System.loadLibrary("QSort"); } publicstatic native voidsort(double[] arr); publicstaticvoidmain(String[] args){ […] double[] arr = new double[Integer.parseInt(args[0])]; Random r = new Random(); for(int i=0; i< arr.length; arr[i++] = r.nextInt(10000)); long s = System.currentTimeMillis(); sort(arr); long e = System.currentTimeMillis(); System.out.println("Sortierung dauerte "+(e-s)+"ms"); } }
Abb. von Referenztypen – z.B. Arrays • Prototyp: • Zugriff auf Array-Elemente: • JNIEXPORT void JNICALL • Java_Sort_sort (JNIEnv *env, jclass, jdoubleArray); • JNIEXPORT void JNICALL • Java_Sort_sort (JNIEnv *env, jclass cl, jdoubleArrayarr){ • jdouble *cArray = (jdouble*) arr; // FALSCH!!! • // arr ist lediglich ein Pointer auf eine JVM- • // Datenstruktur. Der Zugriff auf die Array-Elemente • // muss über die JNIEnv Umgebung erfolgen! • […] • }
Abb. von Referenztypen – z.B. Arrays intcompare(constvoid *a, constvoid *b){ return *((jdouble*) a) < *((jdouble*) b) ? -1 : +1; } JNIEXPORT void JNICALL Java_Sort_sort (JNIEnv *env, jclass cl, jdoubleArrayarr){ jbooleanisCopy; jdouble* cArray = (*env)->GetDoubleArrayElements(env, arr, isCopy); jintlength = (*env)->GetArrayLength(env, array); qsort(cArray, length, sizeof(jdouble), &compare); if(isCopy) (*env)->ReleaseDoubleArrayElements(env, array, cArray, 0); }
Array-Zugriffsmethoden • Get<Type>ArrayRegion, Set<Type>ArrayRegion • Kopiert Bereich aus oder in ein Array mit primitivem Datentyp • Get<Type>ArrayElements, Release<Type>ArrayElements • Erhält einen Pointer auf ein Array (ggf. auch Kopie) und gibt ihn wieder frei (ansonsten Speicherleck!) • GetArrayLength • Ermittelt die Länge eines Arrays • New<Type>Array • Legt ein neues Array an • Nur auf primitive Arrays anwendbar!
Zugriff auf Objekt-Arrays • Beispiel: • String[] strArray; • int[][] intArray; • Object[] objArray; • Unmöglich, gesamtes Array abzufragen • Lediglich einzelne Einträge zugreifbar • GetObjectArrayElement • SetObjectArrayElement
Beispiel: Anlegen eines 2D-Feldes • Zuerst: Anweisung an JVM, ein int[]array anzulegen: • private static native int[][] initInt2DArray(int size); JNIEXPORT jobjectArray JNICALL Java_ObjectArrayTest_initInt2DArray(JNIEnv *env, jclass c, int s){ jobjectArray result; inti; jclassintArrCls = (*env)->FindClass(env, "[I"); if (intArrCls == NULL) return NULL; /* exception thrown */ result = (*env)->NewObjectArray(env, size, intArrCls, NULL); if (result == NULL) return NULL; /* out of memory error thrown */ …
Beispiel: Anlegen eines 2D-Feldes • Dann: Für jeden Eintrag des int[] Arrays ein primitives int[] Array anlegen … for (i = 0; i < size; i++) { jinttmp[256]; /* make sure it is large enough! */ int j; jintArrayiarr = (*env)->NewIntArray(env, size); if (iarr == NULL) return NULL; /* out of memory error */ for (j = 0; j < size; j++) tmp[j] = i + j; (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result; }
Beispiel: Anlegen eines 2D-Feldes • Dann: Für jeden Eintrag des int[] Arrays ein primitives int[] Array anlegen … for (i = 0; i < size; i++) { jinttmp[256]; /* make sure it is large enough! */ int j; jintArrayiarr = (*env)->NewIntArray(env, size); if (iarr == NULL) return NULL; /* out of memory error */ for (j = 0; j < size; j++) tmp[j] = i + j; (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result; } Inhalt von tmp nach iarr kopieren
Beispiel: Anlegen eines 2D-Feldes • Dann: Für jeden Eintrag des int[] Arrays ein primitives int[] Array anlegen … for (i = 0; i < size; i++) { jinttmp[256]; /* make sure it is large enough! */ int j; jintArrayiarr = (*env)->NewIntArray(env, size); if (iarr == NULL) return NULL; /* out of memory error */ for (j = 0; j < size; j++) tmp[j] = i + j; (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result; } Array an übergeordnetes Objekt-Array result übergeben
Beispiel: Anlegen eines 2D-Feldes • Dann: Für jeden Eintrag des int[] Arrays ein primitives int[] Array anlegen … for (i = 0; i < size; i++) { jinttmp[256]; /* make sure it is large enough! */ int j; jintArrayiarr = (*env)->NewIntArray(env, size); if (iarr == NULL) return NULL; /* out of memory error */ for (j = 0; j < size; j++) tmp[j] = i + j; (*env)->SetIntArrayRegion(env, iarr, 0, size, tmp); (*env)->SetObjectArrayElement(env, result, i, iarr); (*env)->DeleteLocalRef(env, iarr); } return result; } Kontrolle über iarr an JVM geben
Arbeiten mit Strings • Java Strings sind Unicode-Codiert, C-Strings sind ASCII-codiert • Anbindung über UTF-8 (kann beide Codierungen abbilden) • GetStringChars, ReleaseStringChars • GetStringUTFChars, ReleaseStringUTFChar • GetStringLength, GetStringUTFLength • NewString, NewStringUTF • GetStringRegion, SetStringRegion • GetStringUTFRegion, SetStringUTFRegion • Anbindung sehr umständlich, Java im Umgang mit Strings komfortabler, daher wenig Relevanz
Zugriff auf Objekt-Felder und -Methoden • Bei komplexen Klassen-Instanzen kann aus C heraus auf die Felder und Methoden der Instanz zugegriffen werden. • Mechanismus erscheint relativ umständlich, daher hier nur exemplarisch dargestellt. Für nähere Infos siehe JNI-Referenz
Zugriff auf Static-Feld • Java-Klasse mit statischem Feld si • si soll in nativer Methode manipuliert werden class StaticFieldAccess { private static intsi; private native void accessField(); public static void main(String args[]) { StaticFieldAccess c = new StaticFieldAccess(); StaticFieldAccess.si = 100; c.accessField(); System.out.println("In Java:"); System.out.println(" StaticFieldAccess.si = " + si); } static { System.loadLibrary("StaticFieldAccess"); } }
Zugriff auf Static-Feld JNIEXPORT void JNICALL Java_StaticFieldAccess_accessField(JNIEnv *env, jobjectobj){ jfieldID fid; /* store the field ID */ jintsi; /* Get a reference to obj’s class */ jclasscls = (*env)->GetObjectClass(env, obj); /* Look for the static field si in cls */ fid = (*env)->GetStaticFieldID(env, cls, "si", "I"); if (fid == NULL) return; /* field not found */ /* Access the static field si */ si = (*env)->GetStaticIntField(env, cls, fid); printf(" StaticFieldAccess.si = %d\n", si); (*env)->SetStaticIntField(env, cls, fid, 200); }
Zugriff auf Instanz-Methode • Methode callback() soll in nativeMethod() gerufen werden • class InstanceMethodCall { • private native void nativeMethod(); • private void callback() { • System.out.println("In Java"); • } • public static void main(String args[]) { • InstanceMethodCall c = new InstanceMethodCall(); • c.nativeMethod(); • } • static { • System.loadLibrary("InstanceMethodCall"); • } • }
Zugriff auf Instanz-Methode JNIEXPORT void JNICALL Java_InstanceMethodCall_nativeMethod(JNIEnv *env, jobjectobj){ jclasscls = (*env)->GetObjectClass(env, obj); jmethodID mid = (*env)->GetMethodID(env, cls, "callback", "()V"); if (mid == NULL) return; /* method not found */ printf("In C\n"); (*env)->CallVoidMethod(env, obj, mid); }
Das war erst der Anfang... • JNI bietet noch mehr: • Erzeugen von Objekten, Aufruf von Konstruktoren • Caching von Methoden-/Feld-Ids • Exception-Handling • … Diese Themen werden dem geneigten Hörer zum Selbststudium überlassen
Fazit • Enorme Möglichkeiten, positive Aspekte von C, C++ und Java zu integrieren • Aufwand allerdings nicht unerheblich, daher sollten immer auch Alternativen in Betracht gezogen werden • Für Standardaufgaben ist Einsatz von JNI aufgrund des stetig abnehmenden Performanz-Vorsprungs von C/C++ (wahrscheinlich) nicht lohnend