Einführung: JNI
Um aus Java heraus nativen Code aufzurufen, wird von Sun die JNI bereitgestellt. JNI steht für Java Native Interface und beinhaltet Spracherweiterungen für Java um C-Code aufzurufen und C-Header um Java-Code von C aus aufzurufen. Für Fortran gibt es nichts dergleichen, deshalb muss man den Fortran-Code mit einem Gerüst aus C umgeben, den Fortran-Code mit dem C-Gerüst in eine Shared Library linken um dann von Java aus über C den Fortran-Code ausführen zu können. Wie das funktinioniert soll hier beschrieben werden.
Ein Beispiel in Fortran
Folgendes Beispiel (fdummy.f) wurde bereitgestellt und von mir um Kontrollausgaben erweitert:C -------------------------------------------------------------------- C FORTRAN DUMMY ROUTINE C -------------------------------------------------------------------- C C VARIABLEN: C C IPAR: PARAMETER (INTEGER) C RPAR: PARAMETER (DOUBLE PRECISION) C IVEC: ARRAY (INTEGER, 1 x IPAR) C RVEC: ARRAY (DOUBLE PRECISION, 1 x IPAR) C IMAT: ARRAY (INTEGER, IPAR x IPAR) C RMAT: ARRAY (DOUBLE PRECISION, IPAR x IPAR) C C -------------------------------------------------------------------- SUBROUTINE FCN(IPAR,RPAR,IVEC,RVEC,IMAT,RMAT) IMPLICIT DOUBLE PRECISION (A-H,O-Z) DIMENSION IVEC(IPAR),RVEC(IPAR),IMAT(IPAR,IPAR),RMAT(IPAR,IPAR) WRITE(*,*) 'F77: DUMMY AUFGERUFEN' RPAR = 0.0D0 WRITE(*,*) 'F77: Float Vektor' WRITE(*,*) (RVEC(i), i=1,10) WRITE(*,*) 'F77: Float Matrix' DO 50 i=1,10 WRITE(*,*) (RMAT(i,j), j=1,10) 50 CONTINUE DO 100 I=1,IPAR IVEC(I) = 0 RVEC(I) = 0.0D0 100 CONTINUE DO 150 I=1,IPAR DO 150 J=1,IPAR IMAT(I,J) = 0 RMAT(I,J) = 0.0D0 150 CONTINUE WRITE(*,*) 'F77: DUMMY WIRD BEENDET' RETURN ENDDieses Beispiel können wir mit dem GNU Fortran77 Compiler in ein Object-File compilieren:
$ g77 -c -fPIC fdummy.fDas erstellt uns ein fdummy.o Object File, womit wir Fortran fürs erste Beiseite legen können.
Das Java-Programm
Zunächst müssen wir uns die C-Funktionen und deren Parameter überlegen, die später die Fortran-Funktion aufrufen. Also müssen wir bereits an diese Funktionen sämtliche Parameter übergeben, die an Fortran weitergegeben werden sollen. Für unser Beispiel genügt eine Funktion cdummy, die wir als private native deklarieren und natürlich nicht implementieren. Außerdem initialisieren wir gleich die JNI indem wir ihr den Namen der Shared Library übergeben (ohne lib am Anfang und .so am Ende. Unsere Library heißt also libldummy.so):class jdummy { private native void cdummy(int i, double d, int i_arr[], double d_arr[], int i_2arr[][], double d_2arr[][]); [...] static { System.loadLibrary("ldummy"); } }Als nächstes eine Funktion
public void run() { [...] }in der wir Arrays erzeugen,
int i_arr[] = new int[MAXSIZE]; double d_arr[] = new double[MAXSIZE]; int i_2arr[][] = new int[MAXSIZE][MAXSIZE]; double d_2arr[][] = new double[MAXSIZE][MAXSIZE];initialisieren
for (int i=0; i<MAXSIZE; i++) { i_arr[i] = i; d_arr[i] = i*1.0; System.out.println(i + " " + i_arr[i] + " " + d_arr[i]); for (int j=0; j<MAXSIZE; j++) { d_2arr[i][j] = i*(j+1)*1.0; i_2arr[i][j] = MAXSIZE*i + j; } }und schließlich
cdummy(...)
aufrufen:
cdummy(MAXSIZE, 5.0, i_arr, d_arr, i_2arr, d_2arr);Im Großen und Ganzen reicht dies aus, um Skalare Werte, Vektoren und Matrizen an ein Fortran-Programm zu übergeben. Hier nochmal der gesamte Code im Überblick (jdummy.java):
class jdummy { final static int MAXSIZE = 10; private native void cdummy(int i, double d, int i_arr[], double d_arr[], int i_2arr[][], double d_2arr[][]); public static void main(String args[]) { jdummy jd = new jdummy(); jd.run(); } public void run() { System.out.println("J: -- Ausfuehren des Java-Code --"); int i_arr[] = new int[MAXSIZE]; double d_arr[] = new double[MAXSIZE]; int i_2arr[][] = new int[MAXSIZE][MAXSIZE]; double d_2arr[][] = new double[MAXSIZE][MAXSIZE]; System.out.println("J: Initialisieren saemtlicher Vektoren und Matrizen"); for (int i=0; i<MAXSIZE; i++) { i_arr[i] = i; d_arr[i] = i*1.0; System.out.println(i + " " + i_arr[i] + " " + d_arr[i]); for (int j=0; j<MAXSIZE; j++) { d_2arr[i][j] = i*(j+1)*1.0; i_2arr[i][j] = MAXSIZE*i + j; } } System.out.println("J: C-Code aufrufen"); cdummy(MAXSIZE, 5.0, i_arr, d_arr, i_2arr, d_2arr); System.out.println("J: -- Zurueck in Java --"); System.out.println("J: Inhalt der beiden Vektoren"); for (int i=0; i<MAXSIZE; i++) System.out.println(i + " " + i_arr[i] + " " + d_arr[i]); System.out.println("J: Ausgabe der zurueckgegebenen int-Matrix"); for(int i=0; i<MAXSIZE; i++) { for(int j=0; j<MAXSIZE; j++) { System.out.print(i_2arr[i][j] + " "); } System.out.println(""); } System.out.println("J: Exit Java"); } static { System.loadLibrary("ldummy"); } }Auch dieses Sourcefile compilieren wir, wie gewohnt mit
$ javac jdummy.java
Die Verbindung: C
Nun benötigen wir nur noch die Verbindung zwischen Java und Fortran: C. Hierfür lassen wir zuerst ein Headerfile erzeugen.$ javah -jni jdummyErstellt eine Headerdatei "jdummy.h" im aktuellen Verzeichnis, die wir gleich in unser C-File einbinden:
#include <stdio.h> #include <stdlib.h> // Header von javah -jni erstellt #include "jdummy.h"Als nächstes fügen wir die Deklaration der Fortranfunktion ein. Die Funktion muss als
extern
deklariert werden und
ans Ende des Funktionsnamens muss ein _
angehängt werden. Variablen müssen grundsätzlich als Referenz übergeben werden,
Arrays als Zeiger.
// = int *, = double * extern void fcn_(int *, double *, int [], double [], int *, double *);Die Definition der Funktion
cdummy
in C kann man einfach aus der von Java generierten Headerdatei kopieren. Lediglich die
Namen der Parameter sollte man einfügen, um innerhalb der Funktion darauf zugreifen zu können:
JNIEXPORT void JNICALL Java_jdummy_cdummy (JNIEnv *env, jobject jobj, // Standard Parameter, muessen bei jedem Aufruf uebergeben werden jint ji, jdouble jd, // Skalare Parameter jintArray ji_a, jdoubleArray jd_a, // Vektoren jobjectArray ji_2a, jobjectArray jd_2a // Die 2-dimensionalen Arrays (/ Matrizen) ) {In dieser Funktion nun erstmal die Daten aus Java holen. Die Skalaren Werte können direkt verwendet werden, die Arrays und Objekte müssen erst kopiert werden:
jsize n = ji; jint *i_a = (*env)->GetIntArrayElements(env, ji_a, 0); jdouble *d_a = (*env)->GetDoubleArrayElements(env, jd_a, 0);Die zweidimensionalen Arrays sind etwas komplizierter, da sie von Java als ObjectArray von Arrays des Grunddatentyps gesehen werden:
jsize i, j; // Die lokalen Arrays initialisieren int imat[n][n]; double dmat[n][n]; // Nun die 2-dimensionalen Arrays for(i=0; i<n; i++) { jintArray i_tmp = (jintArray)((*env)->GetObjectArrayElement(env, ji_2a, i)); jint * ji_tmp = (*env)->GetIntArrayElements(env, i_tmp, 0); for(j=0; j<n; j++) { imat[i][j] = ji_tmp[j]; } } for(i=0; i<n; i++) { jdoubleArray d_tmp = (jdoubleArray)((*env)->GetObjectArrayElement(env, jd_2a, i)); jdouble * jd_tmp = (*env)->GetDoubleArrayElements(env, d_tmp, 0); for(j=0; j<n; j++) { dmat[i][j] = jd_tmp[j]; } }Jetzt kann man die Fortran-Funktion aufrufen und die Skalaren Werte und Arrays übergeben:
fcn_(&n, &jd, i_a, d_a, (int*)imat, (double*)dmat);
Zusammen mit einigen Ausgaben zur Verifikation der übergebenen Daten könnte ein Beispielprogramm so aussehen:
#include <stdio.h> #include <stdlib.h> // Header von javah -jni erstellt #include "jdummy.h" /* * FORTRAN Funktionen muessen als extern deklariert werden * Parameter werden als call-by-reference uebergeben * Damit die Funktion in der Library gefunden wird, muss ein "_" angehaengt werden */ // = int *, = double * extern void fcn_(int *, double *, int [], double [], int *, double *); /* * Um eine C-Funktion von Java aus aufzurufen, muss ihr Name wie folgt zusammengesetzt werden: * "Java_" + Name der Klasse, die den C-Code aufruft + "_" + Name der Funktion * in diesem Fall: * Java_jdummy_cdummy * * Die ersten beiden Parameter muessen immer uebergeben werden, der Rest variiert nach Bedarf, * was man eben vom Java-Programm aus uebergeben will. * Diese Deklaration kann man meist aus dem von javah erzeugten Headerfile kopieren */ JNIEXPORT void JNICALL Java_jdummy_cdummy (JNIEnv *env, jobject jobj, // Standard Parameter, muessen bei jedem Aufruf uebergeben werden jint ji, jdouble jd, // Skalare Parameter jintArray ji_a, jdoubleArray jd_a, // Vektoren jobjectArray ji_2a, jobjectArray jd_2a // Die 2-dimensionalen Arrays (/ Matrizen) ) { printf("C: -- Ab jetzt: C --\n"); // Die Daten aus Java holen // Erst die Eindimensionalen Arrays jsize n = ji; jint *i_a = (*env)->GetIntArrayElements(env, ji_a, 0); jdouble *d_a = (*env)->GetDoubleArrayElements(env, jd_a, 0); jsize i, j; // Die lokalen Arrays initialisieren int imat[n][n]; double dmat[n][n]; // Nun die 2-dimensionalen Arrays for(i=0; i<n; i++) { jintArray i_tmp = (jintArray)((*env)->GetObjectArrayElement(env, ji_2a, i)); jint * ji_tmp = (*env)->GetIntArrayElements(env, i_tmp, 0); for(j=0; j<n; j++) { imat[i][j] = ji_tmp[j]; } } for(i=0; i<n; i++) { jdoubleArray d_tmp = (jdoubleArray)((*env)->GetObjectArrayElement(env, jd_2a, i)); jdouble * jd_tmp = (*env)->GetDoubleArrayElements(env, d_tmp, 0); for(j=0; j<n; j++) { dmat[i][j] = jd_tmp[j]; } } // Zur Probe: Ausgabe einiger Daten printf("C: Inhalt des Integer Arrays, von Java uebergeben:\n"); for (i=0; i<n; i++) printf("%2d %5d\n", i, i_a[i]); printf("C: Inhalt der 2-dim double-Matrix\n"); for (i=0; i<n; i++) { for (j=0; j<n; j++) { printf("%5f ", dmat[i][j]); } printf("\n"); } printf("C: Inhalt der 2-dim int-Matrix\n"); for (i=0; i<n; i++) { for (j=0; j<n; j++) { printf("%5d ", imat[i][j]); } printf("\n"); } printf("-- Aufruf des FORTRAN-Codes --\n"); fcn_(&n, &jd, i_a, d_a, (int*)imat, (double*)dmat); printf("-- Zurueck im C-Programm --\n"); printf("C: Inhalt des Integer-Arrays\n"); for (i=0; i<n; i++) printf("%2d %5d\n", i, i_a[i]); printf("C: Inhalt der 2-dim double-Matrix\n"); for (i=0; i<n; i++) { for (j=0; j<n; j++) { printf("%5f ", dmat[i][j]); } printf("\n"); } printf("C: Inhalt der 2-dim int-Matrix\n"); for (i=0; i<n; i++) { for (j=0; j<n; j++) { printf("%5d ", imat[i][j]); } printf("\n"); } return; }Dieses speichern wir als cdummy.c und compilieren es mit dem GCC:
$ gcc -c -D_REENTRANT -fPIC -I/usr/lib64/jvm/java-1.5.0-sun-1.5.0_10/include -I/usr/lib64/jvm/java-1.5.0-sun-1.5.0_10/include/linuxWobei man u.U. die Pfadangaben bei den
-I
Optionen anpassen muss. Obige Zeile ist korrekt für den WAP-Pool.
Abschließend noch die beiden Object-Files zu einer Library zusammenfügen
$ gcc -shared cdummy.o fdummy.o -lg2c -o libldummy.sound den Suchpfad auf die frisch erstellte Library umlenken:
$ export LD_LIBRARY_PATH=`pwd`Mit
$ java jdummystartet man nun das Java-Programm.
1:
http://java.sun.com/j2se/1.4.2/docs/guide/jni/spec/jniTOC.html
2:
http://en.wikipedia.org/wiki/Java_Native_Interface
3:
http://www.csharp.com/javacfort.html