Jni


 

(Java Native Interface)

 

 

SOMMAIRE

 

I – Présentation
II – Exemple
III – Correspondance des types
	1 – Types primitifs
	2 – Types objets
IV - Chaînes de caractères
	A – Exemple
	B – Quelques fonctions
V - Tableaux
	1 – Tableau d'éléments de types primitifs
	2 – Tableau d'objets
VI – Objets
	A - Exemple
VII - Appels de méthodes
	1 – Méthodes d'instance
		A – Exemple
	2 – Méthodes de classe
	3 – Méthodes d'instance de la super classe
	4 – Correspondance des signatures
VIII – Accéder aux variables
	1 – Variables de classe
		A – Exemple
	2 – Variables d'instance
IX – Levée d'exceptions
	A - Exemple	
X - Références globales et références locales
	1 – Références globales
		A – Exemple
	2 – Références locales
XI – Threads
XII – Invoquer une JVM
XIII – Conclusion

 

PRESENTATION

 

JNI est l'interface qui permet de lier un programme Java à du code natif C et/ou C++.

Un programme Java peut invoquer des fonctions natives et inversement un programme natif peut invoquer des méthodes Java et une JVM.

Une méthode native peut créer des objets Java, elle peut aussi utiliser les objets créés par l'application Java. Les applications Java peuvent traiter les exceptions des méthodes natives.

 

 

D'une façon générale JNI permet:

Attention, la portabilité n'est plus assurée.

 

EXEMPLE

 

L'exemple présenté affiche, sous Windows, "Hello World !" à l'écran. Le programme Java invoque la méthode native écrite en C.

La procédure à suivre est la suivante :

 

 

Programme java :

public class NativeEssai {
	public native void AfficheHello();
	//Chargement de la librairie dynamique hello.dll
	static { System.loadLibrary("hello"); }
	public static void main (String[] args) { new NativeEssai().AfficheHello(); }
}

Fichier Header NativeEssai.h :

#include <jni.h>
#ifndef _Included_NativeEssai
#define _Included_NativeEssai
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: NativeEssai
* Method: AfficheHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_NativeEssai_AfficheHello(JNIEnv *, jobject);
	#ifdef __cplusplus
	}
#endif
#endif

 

Le format de la fonction du fichier d'en-tête est le suivant :

JNIEXPORT <type retourné> JNICALL <prefix>_<nom de la classe>_<nom de méthode>(JNIEnv *, jobject, <Arguments>,<…>,)

Programme Hello.c

#include <jni.h>
#include "NativeEssai.h"
#include <stdio.h>
JNIEXPORT void JNICALL Java_NativeEssai_AfficheHello(JNIEnv *env, jobject obj) {
	printf("Hello World !");
}

 

· JNIEnv *env

Il s ’agit de l ’environnement de la machine Java associé au "Thread" courant, (le Thread ayant engendré l ’appel de la méthode native AfficheHello).

Les méthodes natives manipulent les objets Java à travers le pointeur d'interface env.

 

 

 

C'est par le pointeur env que l'on accéde aux fonctions de JNI.

 

· jobject j

Il s ’agit de l ’objet receveur du message AfficheHello(), ici l ’instance créée dans la méthode main.

 

· En résumé

A chaque appel d ’une méthode native sont transmis par la machine Java un environnement,

l ’objet receveur ou la classe si c'est une méthode classe et éventuellement les paramètres.

 

CORRESPONDANCE DES TYPES

 

 

1 – Types primitifs

Les méthodes natives accédent directement aux types primitifs de Java. Le tableau ci-dessous montre la correspondance.

 

Type Java

Type Natif

Taille en bits

Boolean jboolean 8, unsigned
Byte jbyte 8
Char jchar 16, unsigned
Short jshort 16
Int jint 32
Long jlong 64
Float jfloat 32
Double jdouble 64
Void void n/a

 

2 – Types objets

Les objets sont passés par références. Ils héritent tous du type jobject :

 

 

 

CHAÎNES DE CARACTERES

 

 

Le type jstring est différent du type (char *), aussi les méthodes natives doivent utiliser des fonctions qui assurent la correspondance entre les types.

Par exemple la fonction GetStringUTFChars permet de passer du type unicode au type UTF8 (celui-ci acceptant 0 en fin de chaîne).

A - Exemple :

JNIEXPORT jstring JNICALL Java_Prompt_getLine(JNIEnv *env, jobject obj, jstring prompt) {
	Const char str = (*env)->GetStringUTFChars(env, prompt, 0);
	Printf("%s", str);
	(*env)->ReleaseStringUTFChars(env, prompt, str);
}

La fonction ReleaseStringUTFChars informe la MV qu'elle peut récupérer la mémoire utiliser par prompt.

B - Quelques fonctions

· GetStringUTFChars : Convertit une chaîne unicode en UTF8.

· ReleaseStringUTFChars : "Libère" la place mémoire.

· NewStringUTF : Construit une chaîne.

 

TABLEAUX

 

Comme pour les chaînes de caractères, les tableaux ne sont pas accessibles directement. Il faut utiliser les fonctions de JNI.

1 – Tableau d'éléments de types primitifs

A - Exemple

JNIEXPORT jint JNICALL Java_IntArray_sumArray(JNIEnv *env, jobject obj, jintarray arr) {
	int i, sum = 0;
	jint len = (*env)->GetArrayLength(env, arr);
	jsize *body = (*env)->GetIntArrayElements(env, arr, 0);
	for (i = 0; i <= len; i++) { sum = sum + body[i]; }
	(*env)->ReleaseIntArrayElements(env, arr, body, 0);
	return sum;
}

On récupére, dans un premier temps, la longueur du tableau, puis le tableau lui-même et ensuite on autorise la VM à libérer la mémoire.

La syntaxe des fonctions qui permettent d'acceder aux éléments du tableau est de la forme :

Get<type>ArrayElements

Idem pour la libération de la mémoire :

Release<type>ArrayElements

On peut également travailler sur une partie du tableau. Les fonctions sont du type :

Get/Set<type>ArrayRegion

2 – Tableau d'objets

Il n'est pas possible de récupérer la totalité du tableau. Les fonctions prévues accédent à un élément à la fois.

GetObjectArrayElement

SetObjectArrayElement

 

OBJETS

 

Il est possible de créer des objets Java à partir de JNI à l'aide de la fonction NewObject.

En Java on instancie la classe A de cette façon : ClasseA newObj = new ClasseA(10,"hello");

L'exemple ci-dessous crée un objet de la classe Objet, le renvoi au programme Java qui l'affiche.

A – Exemple

Programmes Java :

public class Appel {
	private native Objet AppelNative();
	public static void main(String[] args) {
		Objet newObj = null;
		Appel a = new Appel();
		newObj = a.AppelNative();
		System.out.println(newObj.getS());
	}
	static { System.loadLibrary("Librairie"); }
}
public class Objet {
	private int i;
	private String s;
	public Objet(int i, String s) {
		this.i = i;
		this.s = s;
	}
	public String getS() { return s; }
}

Programme C :

#include <jni.h>
#include "Appel.h"
JNIEXPORT jobject JNICALL Java_Appel_AppelNative (JNIEnv *env, jobject obj) {
	jclass classe = (*env)->FindClass(env, "Objet");
	jmethodID mid = (*env)->GetMethodID(env, classe, "<init>", "(ILjava/lang/String;)V");
	jint valeur = 10;
	jstring chaine = (*env)->NewStringUTF(env, "hello");
	jobject objet = (*env)->NewObject(env, classe, mid, valeur, chaine);
	return objet;
}

 

APPELS DE METHODES

 

Il est possible d'appeler des méthodes Java à partir d'un programme natif. La récursivité est autorisée. La méthode Java peut appeler à son tour la méthode native.

1 – Méthodes d'instance

La méthode à suivre est la suivante :

1 – Récupérer la classe de l'objet par la fonction GetObjectClass

2 – Rechercher la méthode par la fonction GetMethodID. Si cette méthode n'existe pas, la fonction renvoie 0.

3 – Appeler la méthode par la fonction CallVoidMethod.

La fonction GetMethodID recherche la méthode par son nom et par sa signature.

L'utilitaire javap de Sun donne explicitement le nom et la signature des méthodes.

A – Exemple

L'exemple ci-dessous appelle la méthode Affiche().

Appel.java

class Appel {
	private native void AppelNative();
	private void Affiche() { System.out.println("Méthode Affiche()"); }
	static { System.loadLibrary("Librairie"); }
	public static void main(String[] args) {
		Appel a = new Appel();
		a.AppelNative();
	}
}

Compiled from Appel.java

class Appel extends java.lang.Object {

static {};

/* ()V */

Appel();

/* ()V */

private void Affiche();

/* ()V */

private native void AppelNative();

/* ()V */

public static void main(java.lang.String[]);

/* ([Ljava/lang/String;)V */ }

 

Le programme C :

#include <jni.h>
#include <stdio.h>
#include "Appel.h"
JNIEXPORT void JNICALL Java_Appel_AppelNative (JNIEnv *env, jobject obj) {
	jclass cls = (*env)->GetObjectClass(env, obj);
	jmethodID mid = (*env)->GetMethodID(env, cls, "Affiche", "()V");
	(*env)->CallVoidMethod(env, obj, mid); //Provoque l'affichage de "Méthode Affiche()"
	printf("Fonction AppelNative()"); //Provoque l'affichage de "Fonction AppelNative()"
}

Pour appeler des méthodes qui retournent des valeurs, il faut utiliser les fonctions adéquates :

Call<type>Method

2 – Méthodes de classe

Le principe est le même, il suffit d'utiliser les méthodes :

GetStaticMethodID

CallStatic<type>Method

3 – Méthodes d'instance de la super classe

Il est possible d'appeler une méthode d'instance de la super classe en utilisant la famille de fonctions CallNonvirtual<type>Method. Il aura fallut au préalable récupérer l' ID de la méthode par GetMethodID.

4 – Correspondance des signatures

 

Signature

Type Java

Z Boolean
B Byte
C Char
S Short
I Int
J Long
F Float
D Double
L Nom de la classe; Nom de la classe
[ type Type[]
(types arguments)type retourné Type de la méthode

 

 

ACCEDER AUX VARIABLES

 

 

Il est possible d'acceder, à partir de méthodes natives, aux variables de classe comme aux variables d'instance.

Le principe est le même dans les deux cas seules les fonctions JNI diffèrent.

1 – Variables de classe

Procédure :

Récupérer l'ID de la variable par son nom et sa signature. Puis travailler dessus à l'aide des fonctions appropriées.

A – Exemple

Cet exemple montre comment récupérer la variable si à partir de la méthode native. Affiche sa valeur, la modifie et enfin l'affiche à partir du programme Java.

Programme Java

class FieldAccess {
	static int si;
	private native void accessFields();
	public static void main(String args[]) {
		FieldAccess c = new FieldAccess();
		c.si = 100;
		c.accessFields();
		System.out.println("In Java:");
		System.out.println(" FieldAccess.si = " + FieldAccess.si);
	}
	static { System.loadLibrary("Librairie"); }
}

Programme C

#include <stdio.h>
#include <jni.h>
#include "FieldAccess.h"
JNIEXPORT void JNICALL Java_FieldAccess_accessFields(JNIEnv *env, jobject obj) {
	jclass cls = (*env)->GetObjectClass(env, obj);
	jfieldID fid;
	jint si;
	printf("In C:\n");
	fid = (*env)->GetStaticFieldID(env, cls, "si", "I");
	si = (*env)->GetStaticIntField(env, cls, fid);
	printf(" FieldAccess.si = %d\n", si);
	(*env)->SetStaticIntField(env, cls, fid, 200);
}

Comme d'habitude, on récupère la classe dans cls. On récupère l'ID de la variable dans fid. On récupère si. On l'affiche puis on la modifie.

On notera que les fonctions nécessaires contiennent le mot Static.

2 – Variables d'instance

Le principe est le même. Il suffit d'utiliser les fonctions :

GetFieldID

Get<type>Field

Set<type>Field

Dans les deux cas, on déterminée la signature à l'aide de l'utilitaire javap.

 

 

LEVEE D'EXCEPTIONS

 

Avec JNI on peut lever des exceptions Java. L'exemple suivant lève une exception traitée par java puis revient au programme C.

A – Exemple

Programme Java :

class CatchThrow {
	private native void catchThrow() throws IllegalArgumentException;
	static { System.loadLibrary("Librairie"); }
	public static void main(String args[]) {
		CatchThrow c = new CatchThrow();
		try { c.catchThrow(); } 
		catch (Exception e) { System.out.println("En Java: "); }
	}
}

Programme C :

#include <jni.h>
#include "CatchThrow.h"
JNIEXPORT void JNICALL Java_CatchThrow_catchThrow(JNIEnv *env, jobject obj) { 
	jclass Exc;
	Exc = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
	(*env)->ThrowNew(env, Exc, "\nException en C: "); 
}

On déclare la méthode native pouvant levée une exception de type IllegalArgumentException

La fonction ThrowNew lève l'exception correspondante dans le programme Java, puis affiche le message indiqué.

La fonction ExceptionOccured détermine si une exception à été levée.

ExceptionDescribe affiche la trace de la pile.

ExceptionClear remet à zéro l'exception en cours.

 

 

REFERENCES GLOBALES ET REFERENCES LOCALES

 

 

Par défaut JNI crée des références locales. Ces références sont détruites quand la fonction se termine.

JNI propose des fonctions qui permettent de créer des références globales, c'est dire réutilisables après être sorti de la fonction.

1 – Références globales

A – Exemple

Static jclass cls = 0;
Static jfieldID fid;
JNIEXPORT void JNICALL Java_FieldAccess_accessFields(JNIEnv *env, object obj) {
	If (cls == 0) {
		Jclass cls1 = (*env)->GetObjectClass(env, obj);
		Cls = (*env)->NewGlobalRef(env, cls1);
		Fid = (*env)->GetStaticFieldID(env, "si", "I");
	}
}

Au deuxième appel de la fonction cls et fid pointe sur les bons objets.

2 – Références locales

La fonction DeleteLocalRef(env, <objet>) autorise la récupération de la mémoire par le ramasse miettes de Java.

 

 

THREADS

 

JNI à l'équivalent du mot-clé synchronised de Java.

(*env)->MonitorEnter(env, obj);

. . .

(*env)->MonitorExit(env, obj);

est équivalent à :

synchronised (obj) {

. . .

}

- Le pointeur d'interface (JNIEnv *) n'est valide que dans le thread courant.

Il n'est pas possible de passer ce pointeur d'un thread à un autre.

- Il n'est pas possible non plus de passer une référence locale d'un thread à un autre.

On doit la transformer en référence globale.

 

INVOQUER UNE JVM

 

Une caractéristique importante de JNI est d'autoriser le lancement de Java Virtual Machine à partir d'un programme natif.

L'exemple ci-dessous montre comment invoquer une jvm sous Windows à partir d'un programme C/C++ :

La marche à suivre est la suivante :

- Définir les pointeurs nécessaires au lancement de la jvm

- Initialiser les arguments

- Charger la libraire jvm.dll

- Initialiser les pointeurs

- Créer la jvm

- Invoquer la méthode main du programme Java

- Détruire la jvm

Programme C :

#include <jni.h>
#include <windows.h>
#define PATH_SEPARATOR ';' /* platform dependent */
#define USER_CLASSPATH "." /* directory where Sample.class is present */
typedef jint (*P_JNI_GetDefaultJavaVMInitArgs)(void *args);
typedef jint (*P_JNI_CreateJavaVM)(JavaVM **pvm, JNIEnv ** penv, void *args);
void __cdecl main(int argc, char **argv) {
	char classpath[1024]; /* Class Path */
	JNIEnv *env = NULL; /* Environment */
	JavaVM *jvm = NULL; /* Virtual Machine */
	JDK1_1InitArgs vm_args; /* Initializing arguments */
	Jint res = -1;
	jclass cls;
	jmethodID mid;
	jstring jstr;
	jobjectArray args;
	HANDLE hLib = NULL; 
	/* Pointer to required functions */
	P_JNI_GetDefaultJavaVMInitArgs pfnGetDefaultJavaVMInitArgs = NULL;
	P_JNI_CreateJavaVM pfnCreateJavaVM = NULL;
	/* Load the library */
	printf("Loading Library .... <%s>\n",argv[1]);
	hLib = LoadLibrary(argv[1]);
	/* Store the function pointer for getting default arguments */
	pfnGetDefaultJavaVMInitArgs = (P_JNI_GetDefaultJavaVMInitArgs)GetProcAddress(hLib,"JNI_GetDefaultJavaVMInitArgs");
	/* IMPORTANT: specify vm_args version # if you use JDK1.1.2 and beyond */
	vm_args.version = 0x00010001;
	/* Get the default arguments */
	if(pfnGetDefaultJavaVMInitArgs != NULL)(*pfnGetDefaultJavaVMInitArgs)(&vm_args);
	/* Append USER_CLASSPATH to the end of default system class path */
	if(vm_args.classpath != NULL) sprintf(classpath, "%s%c%s", vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
	else sprintf(classpath, "%s", USER_CLASSPATH);
	vm_args.classpath = classpath;
	/* Store the function pointer for creating the VM */
	pfnCreateJavaVM = (P_JNI_CreateJavaVM) GetProcAddress(hLib, "JNI_CreateJavaVM");
	/* Create the Java VM */
	if(pfnCreateJavaVM != NULL) res = (*pfnCreateJavaVM)(&jvm,&env,&vm_args);
	cls = (*env)->FindClass(env, argv[2]);
	mid = (*env)->GetStaticMethodID(env, cls, "main", "([Ljava/lang/String;)V");
	jstr = (*env)->NewStringUTF(env, "Hello World!!!");
	args = (*env)->NewObjectArray(env, 1, (*env)->FindClass(env, "java/lang/String"), jstr);
	(*env)->CallStaticVoidMethod(env, cls, mid, args);
	(*jvm)->DestroyJavaVM(jvm);
}

Programme Java :

import java.lang.*;
import java.io.*;
public class Sample {
	static public void main (String argv[])	{
		System.out.println("Version : " + System.getProperty("java.version"));
		System.out.println("Vendeur : " + System.getProperty("java.vendor"));
	}
}

Programme Batch pour le jdk 1.2 :

@ECHO OFF

IF "%OLDPATH%" == "" SET OLDPATH=%PATH%

SET PATH=E:\JAVA1~1.2\BIN;E:\JAVA1~1.2\JRE\BIN\CLASSIC;%OLDPATH%

SET CLASSPATH=.;

SET JVMDLL=jvm

SET PATH

SET CLASSPATH

INVOKEJVM %JVMDLL% Sample

 

CONCLUSION

 

JNI propose toutes les fonctions et les types nécessaires permettant le passage de Java vers C/C++ et C/C++ vers Java.

Ces fonctions font parties d'une structure accessible via le pointeur d'environnement env.

Bien que les méthodes natives soient capables de faire plein de choses que Java ne fait pas. Leurs utilisations rend les programmes moins portables, moins sécurisés et surtout plus compliqués à développer et à maintenir.

D'autre part, chaque vendeur de jvm doit implémenter les fonctions de JNI, aussi il est tout à fait possible que ces implémentations varient d'un constructeur à l'autre. De même qu'à chaque version du jdk JNI peut changer.

 

 


Bibliographie:

La spécification:

http://java.sun.com/products/jdk/1.2/docs/guide/jni/index.html

Le tutorial de Sun:

http://java.sun.com/docs/books/tutorial/native1.1/index.html

Code JVM:

http://codeguru.developer.com/java/articles/216.shtml

Notes de cours de Jean-Michel Douin:

http://java.cnam.fr/public_html/Iagl99/douin/java_jni.zip

 


Téléchargement