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