Java Native Interface Appunti Riccardo Rizzo 1/8 Introduzione L'uso delle Java Native Interface e' giustificato tutte quelle volte che una applicazione non puo' essere scritta interamente in Java. Per esempio quando: • • • la libreria di classi Java standard non supporta delle carateristiche dipendenti dalla piattaforma (hardware particolari); si hanno gia' delle librerie scritte in altri linguaggi; si vuole implementare del codice crtitico in un linguaggio di piu' basso livello. La libreria JNI e' il ponte fra la JVM ed il codice nativo, cioe' il codice compilato specificamente per una particolare macchina (hardware e sistema operativo). Attraverso la interfaccia JNI codice che gira in una JVM puo' invocare codice esterno (in altro linguaggio) e, attraverso un particolare meccanismo detto "invocation API", codice in altro linguaggio puo' richiamare la JVM e le applicazioni Java. Accessibilita' delle librerie Java, delle classi e della JVM da un programma in C (dal tutorial della Sun) Accessibilita' delle funzioni in codice nativo dalle classi Java (dal tutorial della Sun) Nello scrivere codice che usa codice in altro linguaggio diverso dal Java, usando i metodi nativi, occorre ricordare che i vicoli relativi alla sicurezza vengono abbattuti: il codice nativo non e' soggetto ai meccanismi di controllo cui e' soggetto il linguaggio Java. Inoltre non e' consigliabile usare metodi nativi nelle applet poiche' l'utente dovrebbe prelevare dalla rete anche il codice nativo. 2/8 Gli esempi negli appunti sono relativi alla comunicazione fra linguaggio Java e linguaggio C, anche se e' possibile scrivere scrivere metodi nativi in C++. I passaggi necessari alla scrittura sono indicati nella figura (dal Tutorial della Sun) : Dalla figura sopra e' chiaro che e' generata una classe Java che ingloba una o piu' chiamate ad una routine esterna. E' da evitare l'uso di una classe contenitore che raccolga tutte le chiamate esterne, sarebbe bene invece continuare ad usare la filosofia degli oggetti in Java. Vediamo un esempio relativo alla stampa di un messaggio da parte di una applicazione in C (Hello World). 3/8 Scrittura del codice Java L'esempio di codice e' costituito da tre righe: //Hello.java //prova del funzionamento delle routines JNI class Hello { //metodo nativo sayHello che non e' implementato //all'interno della classe public native void sayHello(); //richiama la libreria statica hello (hello.dll in windows) static {System.loadLibrary("hello"); } //main della classe Hello public static void main(String[] args){ Hello h=new Hello();//istanzia un oggetto di tipo Hello h.sayHello(); //si richiama il metodo esterno sayHello System.out.println("eseguita routine"); } } La prima riga definisce il metodo nativo e ne determina la firma. La parola chiave native indica che sara' implementato con codice nativo ed indica al compilatore di non cercare il codice del metodo. Nella seconda riga si carica la routine in codice nativo. L'argomento di System.loadLibrary e' il nome della routne che verra' convertito in quello convenzionale per la piattaforma (per Windows hello.dll, per Solaris libhello.so). Per mezzo della parola chiave static si garantisce che quando viene caricata la classe nel sistema e' caricata anche la dll contenente il metodo nativo. Il main istanzia l'oggetto e richiama il metodo nativo. Compilazione Si compila la classe Hello.java usando javac Hello.java Creazione dell' Header Il programma javah genera un file header Hello.h a partire dal file class, l' header contiene una firma del metodo che il programma C deve rispettare. La riga di comando da usare e': javah -jni Hello Il file generato e' del tipo : 4/8 /* 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: sayHello * Signature: ()V */ JNIEXPORT void JNICALL Java_Hello_sayHello (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif La riga che contiene la firma del metodo e' JNIEXPORT void JNICALL Java_Hello_sayHello (JNIEnv *, jobject); Il nome del metodo, che deve essere rispettato nella creazione del programma in C, e' composto in questo modo: La dichiarazione del metodo implica la presenza di due parametri: uno di tipo JNIEnv e l'altro di tipo jobject, il significato di questi parametri sara' chiarito in seguito. 5/8 Scrittura del metodo nativo Il file in linguaggio C e' di questo tipo: // File Hello.c #include <jni.h> #include "Hello.h" #include <stdio.h> JNIEXPORT void JNICALL Java_Hello_sayHello (JNIEnv *env, jobject obj) { printf("Ciao Mondo\n"); return; } E' necessario l'include dell'header jni.h che contiene le informazioni necessarie per fare interagire C con Java. Il nome della funzione in C deve rispettare quello generato nell'header: JNIEXPORT void JNICALL Java_Hello_sayHello (JNIEnv *env, jobject obj) i parametri passati hanno il seguente significato: JNIEnv *env Puntatore utile a dare l'accesso ad un insieme di funzioni JNI che consentono di convertire i dati piu' complessi del Java a strutture usabili in C jobject obj Riferimento all'oggetto chiamante, analogo al riferimento this in Java. Guardando l'esempio, questo riferimento e' relativo all'oggetto h generato dal programma in Java. Creazione della libreria condivisa Nellesempio si suppone di usare il compilatore c Microsoft Visual C++ 4.0 liberamente disponibile al sito Microsoft. La creazione della libreria dll si ottiene con la seguente riga di comando: cl -Ic:\java\include -Ic:\java\include\win32 -LD Hello.c -Fehello.dll cl Invoca il compilatore C java Deve essere sostituito con il percorso del compilatore java -LD Indica al compilatore di creare una dll -Fe Indica il nome della dll generata dal compilatore 6/8 Esecuzione della applicazione La applicazione puo' essere eseguita come una normale applicazione java con il comando: java Hello Passaggio parametri fra C e Java Vediamo adesso come e' possibile scambiare informazioni fra il programma Java ed il programma in linguaggio C. Rifacendoci all'esempio precedente se il metodo sayHello prendesse un parametro intero, cioe' avesse una firma del tipo: public native void sayHello(int v) la dichiarazione in C nel file header sarebbe del tipo: JNIEXPORT void JNICALL Java_Hello_sayHello (JNIEnv *, jobject, jint); dove jint e' il nome C per il tipo primitivo int in Java. I nomi degli altri tipi primitivi sono: Table 1Primitive Types and Native Equivalents (dalla documentazione Java) Java Type Native Type boolean jboolean byte jbyte char jchar short jshort int jint long jlong float jfloat double jdouble void void Size in bits 8, unsigned 8 16, unsigned 16 32 64 32 64 n/a Esempio: una funzione che calcola la radice quadrata Si suppone di usare la funzione C che calcola la radice quadrata di un numero reale e di restituire il risultato ad un programma in Java. In questo caso occorre passare un argomento a alla routine in C e prelevare il risultato. Il programma in Java potrebbe essere il seguente: //File Sqrt.java public class Sqrt{ public native double sqrt(double d); 7/8 static{ System.loadLibrary("sqrt"); } public static void main (String[] args){ Sqrt s=new Sqrt(); double res=s.sqrt(9.1); System.out.println("Radice di 9.1=" +res); } } Mentre il file in linguaggio C che richiama la funzione e' del tipo: //File Sqrt.c #include <jni.h> #include "Sqrt.h" #include <math.h> JNIEXPORT jdouble JNICALL Java_Sqrt_sqrt jdouble d) { return sqrt(d); } (JNIEnv *env, jobject jobj, Notare come il programma accetti un parametro di tipo jdouble e restituisca un jdouble al programma chiamante. 8/8