JNI : échanges entre Java et C++

Inspiré d'un article sur le site de Sun. On y trouve aussi un exemple commenté alors que d' autres exemples sont accessibles sur un site marseillais.

La communication entre java et C consiste à pouvoir appeler, à partir d'un code java, un sous-programme écrit en C, ou à pouvoir utiliser une classe java (champ et méthode) à partir d'un programme écrit en C.
Bien évidemment il faut aussi permettre le passage de paramètres c'est à dire utiliser ou créer, en langage C des éléments java (types de base et objets).

Ce problème d'interface a deux volets; d'une part, du point de vue compilation, il faut disposer en C des équivalents des types de base et du type 'String' de java; d'autre part, du point de vue exécution il faut soit 'adjoindre' à la machine virtuelle java le code machine issu du C (si l'échange est initié coté java) soit lancer une machine virtuelle java à partir d'un processus issu de C (si l'échange est initié coté C).

L'utilisation de sous-programmes écrits en C, à partir d'un programme écrit en Java demande d'abord de déclarer en Java une classe et une méthode, puis d'appeler cette méthode. Ensuite il faut savoir comment programmer la fonction C pour utiliser les paramètres qu'elle reçoit de l'appel en Java. En résumé il faut une réponse aux questions suivantes.

L'utilisation d'objets écrits en Java, à partir d'un programme écrit en C demande de connaître la représentation en mémoire des objets créés par la machine virtuelle Java. Ensuite, au moment de l'exécution, il faut activer ces objets Java, c'est à dire lancer une machine virtuelle, y charger une classe, puis un objet, afin d'accéder à un champ ou une méthode.

La réponse à ces questions se trouve dans l'environnement JNI qui comprend évidemment une partie Java (commandes javah, javap) et une partie C (fichier jni.h ...).  

Les étapes

Ce paragraphe présente l'utilisation, en Java, d'une méthode simple dont le code est écrit en C (nous parlons de fonction C), afin d'illustrer le passage du nom en java au nom en C (qui reflète nom de classe et nom de méthode) et l'adjonction à la machine virtuelle java du code C.
On utilise l'environnement C++.

Pour satisfaire les exigences des deux langages, on commence par écrire, et compiler une classe Java. Puis un fichier d'entête contenant la déclaration de la fonction C est générée automatiquement par l'utilitaire javah. On écrit ensuite le code de la fonction C, on le compile et on l'inclut dans une bibliothèque partagée. Enfin la machine virtuelle java est lancée, de façon qu'elle ait connaissance de cette bibliothèque.

Nous appelons Coucou.java le fichier contenant la classe Coucou et sa méthode affiche() qui doit va implantée en C; nous appelons coucou.h le fichier d'en-tête issu de cette classe, et coucou.C le fichier C++ qui définit le code de la fonction C associée à la méthode java affiche().

Voyons ces étapes en détail.

  1. Ecrire le code de la classe java
    Repérer le mot 'native' dans le prototype de afficher, et le nom coucou de la bibliothèque chargée, qui correspond au fichier de   libcoucou.so.
    /* Coucou.java : première utilisation de JNI */
    
    class Coucou {
    private native void affiche();
    public static void main(String args[]) {
       new Coucou().affiche();
       }
    static { System.loadLibrary("coucou"); }
    }
    
    et compiler la classe java:
        java Coucou.java

  2. Générer le fichier d'entête coucou.h, à partir de la classe Coucou
        javah -o coucou.h Coucou

    Voici le fichier généré:
    /* DO NOT EDIT THIS FILE - it is machine generated */
    #include <jni.h>
    /* Header for class Coucou */
    
    #ifndef _Included_Coucou
    #define _Included_Coucou
    #ifdef __cplusplus
    extern "C" {
    #endif
    /*
     * Class:     Coucou
     * Method:    affiche
     * Signature: ()V
     */
    JNIEXPORT void JNICALL Java_Coucou_affiche
      (JNIEnv *, jobject);
    
    #ifdef __cplusplus
    }
    #endif
    #endif
    

    Remarquer le nom 'Java_Coucou_affiche' composé avec le nom de la classe et celui de la méthode; remarquer les paramètres relatifs à l'environnement, de type JNIEnv* et jobject, qui ne sont pas utilisés dans cet exemple simple.

  3. Ecrire le source en C++, et le compiler

    Après avoir copié sur le fichier d'entête crée par javah le prototype associé à la méthode affiche(), on code le source, sans oublier d'inclure "coucou.h" et <jni.h>

    /* coucou.C */
    
    #include <jni.h>
    #include "coucou.h"
    #include <iostream.h>
    
    JNIEXPORT void JNICALL Java_Coucou_affiche (JNIEnv *, jobject) {
       cout<<"Depuis code C++ \n   ... j'affiche ...\n   COUCOU"<<endl;
    }
    
    Dès qu'on a le nom du répertoire contenant le fichier jni.h; (dans la distribution des fichiers du JDK) on peut compiler.
    Pour le système AIX et l'environnement xlC, la commande est:
        xlC   -c   &-I/usr/java130/include   coucou.C
    Pour le système linux et l'environnement GNU, la commande est:
        g++   -c   -I/usr/java/j2sdk1.4.1_06/include   coucou.C

  4. Créer une bibliothèque partagée contenant le module objet

    La commande dépend du système d'exploitation et de l'environnement de programmation C utilisé. Nous créons, dans le sous-répertoire , la bibliothèque de nom libcoucou.so.
    Pour le système AIX et l'environnement xlC, la commande est:
        makeC++SharedLib -p 1 -o bib/libcoucou.so   coucou.o
    Pour le système linux et l'environnement GNU, la commande est:
        g++ -shared -o bib/libcoucou.so   coucou.o

  5. Exécuter

    L'exécution se fait après avoir rendu accessible à la machine virtuelle java le code créé; le chargement de la classe Coucou est standard.
    Pour le système AIX, les commandes sont:
        export LIBPATH=~/app/java/jni/bib
        java Coucou
    Pour le système linux, les commandes sont:
        export LD_LIBRARY_PATH=~/app/java/jni/bib
        java Coucou


Echange de chaînes entres java et C++

Cet exemple montre comment, dans une fonction C, utiliser un objet String de java, et comment en construire un, qui est la valeur de retour d'une fonction.
Il faut écrire en C le code de la méthode 'String saisirLigne(String msg);' (prototype Java) qui affiche le texte msg, puis saisit une ligne au clavier, et renvoie les caractères saisis sous forme d'objet Java 'String'.


Nous notons SaisirChaine.java le fichier java, ainsi que saisirChaine.h saisirChaine.C les fichiers utilisés en C++. La bibliothèque partagée s'appelle libsaisir.so.

Le code Java, écrit ci-après, est simple.

/* SaisirChaine.java    passage de chaînes entre Java et C++
Résumé des opérations (répertoire courant ~/app/java/jni)
  Ecrire et compiler classe java: javac SaisirChaine.java
  Générer le fichier d'entête :   javah -o saisirChaine.h SaisirChaine
  Ecrire le source en C++, saisirChaine.C, et le compiler
      xlC -c -I/usr/java130/include saisirChaine.C
  Créer bibliothèque: makeC++SharedLib -p 1 -o bib/libsaisir.so saisirChaine.o
  Exécuter: export LIBPATH=~/app/java/jni/bib
            java SaisirChaine	Chaine
*/

class SaisirChaine {
private native String saisirLigne(String msg);
public static void main(String args[]) {
   SaisirChaine p = new SaisirChaine();
   String ligne = p.saisirLigne("Ligne: ");
   System.out.println("A été tapé: " + ligne);
   }
static { System.loadLibrary("saisir"); }
}


Dans le fichier d'entête généré par javah Java_SaisirChaine_saisirLigne est le nom de la fonction, et le type jstring (défini dans jni.h) correspond à la classe String de Java.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class SaisirChaine */

#ifndef _Included_SaisirChaine
#define _Included_SaisirChaine
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     SaisirChaine
 * Method:    saisirLigne
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_SaisirChaine_saisirLigne
  (JNIEnv *, jobject, jstring);

#ifdef __cplusplus
}
#endif
#endif
 

Voyons dans le source en C++, comment est utilisé le paramètre msg_java de type 'jstring', qui représente l'objet de la classe String' de Java. Nous appelons descripteur de chaîne ce type jstring, à partir duquel on extrait, par la fonction GetStringUTFChars, la suite d'octets, représentant la chaîne de caractères en C(avec le codage UTF8), de type 'char *'.

On distingue évidemment, le type jstring descripteur d'une chaîne Java, et le type char* qui représente une chaîne en C.
Après utilisation, les zones mémoire (msg_data, et msg) utilisées dans la programmation C doivent être libérées par la fonction ReleaseStringUTFChars.

Nous avons également, dans le source ci-dessous, l'utilisation de la fonction NewStringUTF pour créer un objet String de Java (type jstring en C), à partir d'une chaîne en C (cf tableau ligne).

/* saisirChaine.C   chaines entre java et C++ */

#include <jni.h>
#include "saisirChaine.h"
#include <iostream.h>

JNIEXPORT jstring JNICALL Java_SaisirChaine_saisirLigne(
   JNIEnv *penv, jobject obj, jstring msg_java) {
char ligne[1024+1];
const char * msg = penv->GetStringUTFChars(msg_java,0);

cout << "\nSaisie depuis C++\n   "<<msg;  
penv->ReleaseStringUTFChars(msg_java,msg);
cin.getline(ligne,sizeof(ligne));
return penv->NewStringUTF(ligne);
}


Utilisation de tableaux

Les tableaux sont des objets particuliers de java, qui ne correspondent pas à des classes. Cet exemple montre comment utiliser en C un paramètre de type tableau. L'information associée à un paramètre tableau en Java est l'adresse d'une zone mémoire où figurent tout ce qui est nécessaire pour l'accès à un élément, comme par exemple, la taille du tableau (Java détecte la validité ou non de l'indice fournit), l'adresse de l'élément d'indice 0, ...
Nous appelons descripteur de tableau cette information.

Nous notons UtilTab.java le fichier contenant la classe Java, et utilTab.h, UtilTab.C les fichiers contenant le code C++; la bibliothèque est notée libutil.so.

Dans le code java ci-dessous, la méthode sommeTab prend un tableau d'entiers en paramètre et renvoie la somme de ces éléments.

/*  UtilTab.java   une tableau entre java et C++
Ecrire le code de la classe java
Compiler la classe java     : javac UtilTab.java
Créer le fichier d'entête   : javah -o utilTab.h UtilTab
Ecrire le source en C++     fichier utilTab.C
                            xlC -c -I/usr/java130/include utilTab.C
Créer une bibliothèque      : makeC++SharedLib -p 1 -o libutil.so utilTab.o
Exécuter                    : java UtilTab

Remarques
=========
   avec g++ l'obtention de bibliothèque partagée est:
       g++ -c -I/usr/java130/include utilTab.C   # compilation
       g++ -shared -o libutil.so utilTab.o       # bibliothèque
   on peut mettre plusieurs modules objets dans une seule bibliothèque

*/
class UtilTab {
private native int sommeTab(int t[]);
public static void main(String args[]) {
   UtilTab p = new UtilTab();
   int t[] = new int[] { 5,15,7,13 };
   System.out.println("Somme calculée: " + p.sommeTab(t));
   }
static { System.loadLibrary("util"); }
}
 

Le fichier d'entête généré par javah figure ci-après. Y voir le nom de la méthode, Java_UtilTab_sommeTab et le type jintArray du descipteur de tableau d'entiers en Java.

/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class UtilTab */

#ifndef _Included_UtilTab
#define _Included_UtilTab
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     UtilTab
 * Method:    sommeTab
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_UtilTab_sommeTab
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

Nous voyons dans le code C++, l'utilisation de la fonction GetArrayLength qui donne la taille du tableau, et l'utilisation de GetIntArrayElements pour obtenir l'adresse du premier octet , type jint* de la version C++ du tableau. Ces informations sont obtenues à partir du parametre t_java de type jintArray, qui est le descripteur du tableau d'entiers.

Après utilisation, les zones mémoire (t_java, et t) utilisées dans la programmation C doivent être libérées par la fonction ReleaseIntArrayElements. En effet la gestion de ces zones, allouées par les routines de JNI, doit être cohérente avec la gestion mémoire de la machine virtuelle Java.

Ainsi, comme pour les chaînes, il faut distinguer:
    le type jintArray qui représente un descripteur
    du type jint* qui représente un tableau en C.
Evidemment le type jintArray porte plus d'informations que le type jint*, puisque le premier nous permet d'avoir la taille du tableau.

Signalons aussi que pour chaque type basique (boolean, char ... double) il existe:
un type pour le descripteur:
    jbooleanArray jcharArray ... jdoubleArray
des fonctions associées:
    GetBooleanArrayElements ... GetDoubleArrayElements
un type pour le tableau en C:
    jboolean* jchar* ... jdouble*
Enfin, pour traiter un objet de classe quelconque, il existe le type jobjectArray qui représente le descripteur d'un tableau d'objets en Java, la fonction GetObjectArrayElements, et le type jobject* qui représente un tableau.

 /* utilTab.C */

#include <jni.h>
#include "utilTab.h"
#include <iostream.h>

JNIEXPORT jint JNICALL Java_UtilTab_sommeTab
  (JNIEnv * penv, jobject obj, jintArray t_java) {
jsize lg = penv->GetArrayLength(t_java);
int somme,i;

jint *t = penv -> GetIntArrayElements(t_java,0);
for( i=0,somme=0; i<lg; i++) cout<<t[i]<<" ",somme += t[i];
penv->ReleaseIntArrayElements(t_java,t,0);
cout <<"   calcul effectué dans une fonction de utilTab.C"<<endl;
return somme;
}

 

Utilisation de champs ou méthodes d'un objet

A partir d'un source C++ on peut atteindre les champs d'un objet et les champs statiques d'une classe java, soit pour consultation soit pour modification. On peut, de façon analogue, appeler une méthode d'un objet ou une méthode statique d'une classe.

Pour cela il faut à partir de l'objet et du nom de champ concerné, obtenir, son "identification en C", qui permettra, ensuite de consulter ou modifier le champ, soit directement pour un type basique, soit par l'intermédiaire du descripteur pour une chaîne ou un tableau.
Pour activer une méthode, il faut, de façon analogue, avoir son "identification en C", à partir de l'objet et du nom de la méthode; cependant, à cause de la surcharge possible du nom, il faut préciser de plus la signature de la méthode recherchee.


La signature d'une méthode tient compte des types de chaque paramètre et de la valeur renvoyée. Par exemple la signature de la méthode:   void appelCpp(double)   est codée:   (D)V

Le codage de la signature obéit à quelques conventions (voir en annexe):

Par exemple   ([Ljava/lang/String,I)D   désigne une méthode ayant deux paramètres ( un tableau de chaines et un entier), qui renvoie un double.

Dans l'exemple ci-dessous un champ 'double' et un champ 'tableau de double' sont modifiés.

La commande   javap   permet de connaître les signatures des champs et méthodes d'une classe.
Voir ci-dessous le codage de la signature de chaque champ de la classe UtilObj, obtenu par la commande
    javap -s -p UtilObj

class UtilObj extends java.lang.Object
signature codage
double d_java;   D
double td_java[];   [D
static int i_java_stq;   I
UtilObj();   ()V
private native void appelCpp(double);   (D)V
static void aff();   ()V
void aff(java.lang.String);   (Ljava/lang/String;)V
public static void main(java.lang.String[]);   ([Ljava/lang/String;)V


Dans la classe java ci-après, les champs d_java et td_java sont modifiés par la méthode appelCpp; remarquons que le tableau td_java est créé avant d'être modifié par appelCpp.

Nous avons aussi deux méthodes aff, avec des protoypes différents; elles sont appelées par appelCpp pour montrer comment les distinguer grace à leur signature.
L'une des méthodes est statique (méthode de classe) afin de montrer comment la distinction se fait, dans la programmation C, entre l'utilisation d'un élément (champ ou méthode) statique et celle d'un élément associé à un objet.

/*  UtilObj.java   utilisation de champs à partir de code C++ */
class UtilObj {
double d_java;
double td_java[]=new double[3];
static int i_java_stq;

/* modifie d_java par résultat d'un calcul basé sur d */
private native void appelCpp(double d);
static void aff() {
   i_java_stq++;
   System.out.println("Nb.appels à 'static void aff()' :"+i_java_stq);
   }
void aff(String msg) {
   System.out.print(msg+d_java+"  [");
   for(int i=0;i<td_java.length;i++)System.out.print("  "+td_java[i]);
   System.out.println("]");
   }
public static void main(String args[]) {
   UtilObj u = new UtilObj();
   u.d_java=-3.14; u.appelCpp(3.14);
   u.appelCpp(2.718);
   }
static { System.loadLibrary("util"); }
}


Le fichier utiliObj.C ci-après, contient le code de la fonction Java_UtilObj_appelCpp qui définit la méthode appelCpp de la classe UtilObj.

Le premier groupe d'instructions modife le champ d_java de type double; la variable i_d_java est l'"identification en C" (type jfieldID qui permet d'utiliser ce champ; sa modification, qui n'est pas une affectation directe, se fait par la fonction SetDoubleField. On voit également l'utilisation de GetDoubleField pour connaître la valeur du champ.

Ensuite on modifie le contenu du tableau td_java, après avoir obtenu son "identification en C" dans la variable _td_java. Pour cela, on recupère d'abord un "desripteur" du tableau dans la variable td_java de type jdoubleArray
Puis, comme dans l'exemple précédent d'utilisation de tableau, on accède à la longueur (variable lg) et à l'adresse du premier octet (variable td).

/* utilObj.C */

#include <jni.h>
#include "utilObj.h"
#include <iostream.h>

/* Calcul à partir de d et résultat dans le champ 'double d_java' */
JNIEXPORT void JNICALL Java_UtilObj_appelCpp
  (JNIEnv *penv, jobject obj, jdouble d) {
int i;

jclass i_classe= penv->GetObjectClass(obj);

// Modification d'un champ de type de base
jfieldID i_d_java=penv->GetFieldID( i_classe,"d_java","D");
if(i_d_java==0) return;
cout <<"Dans source C++, récupéré: "<<penv->GetDoubleField(obj,i_d_java)<<endl;
penv->SetDoubleField(obj,i_d_java,2*d);
cout <<"   champ modifié: "<<penv->GetDoubleField(obj,i_d_java)<<endl;

// Modification d'un tableau de type de base
jfieldID i_td_java=penv->GetFieldID( i_classe,"td_java","[D");
if(i_td_java==0) return;
jdoubleArray td_java=jdoubleArray(penv->GetObjectField(obj,i_td_java));
jsize lg= penv->GetArrayLength(td_java);
cout<<"   nb.éléments: "<<lg<<endl;;
jdouble *td = penv -> GetDoubleArrayElements(td_java,0);
for(i=0; i<lg; i++) td[i]=d;
penv->ReleaseDoubleArrayElements(td_java,td,0);

// Utilisation d'une méthode de l'objet obj
jmethodID i_methode=penv->GetMethodID(i_classe,"aff","(Ljava/lang/String;)V");
if(i_methode==0) return;
jstring msg= penv->NewStringUTF("   utilObj.C appelle méthode java ");
penv->CallVoidMethod(obj,i_methode, msg);
penv->ReleaseStringUTFChars(msg,NULL);

// Appel d'une méthode statique de la classe UtilObj (ou méthode de classe)
i_methode=penv->GetStaticMethodID(i_classe,"aff","()V");
if(i_methode==0) return;
penv->CallStaticVoidMethod(i_classe,i_methode, msg);
} 
 

Appel Java depuis C: lancer une machine virtuelle Java

On peut avoir aussi la possibilité qu'un processus issu d'un programme en C utilise des classes java. Pour cela le processus doit activer la machine virtuelle java, puis utiliser, comme nous avons vu ci-dessus, des méthodes de classes Java .
Le lancement de la machine virtuelle java est nouveau.


Pour illustrer le lancement d'un M.V.J, nous utilisons une petite classe, dont le source est ci-dessous; puis nous voyons la programmation en C++ du lancement.

public class AppelMV {
public static void main(String[] mots) {
   System.out.println("Coucou !!! " + mots[0]);
   }
}

/* appelMV.cc
   xlC -c -I/usr/java130/include appelMV.cc

http://www.nawouak.net/?doc=bpp_library+lang=fr
Utilisation windows
http://penserenjava.free.fr/pens/indexMain_18&0.htm

#linux
   export RJH=/usr/java/j2sdk1.4.1_06/
   #compilation
   export RJI=${RJH}include
   g++ -Wno-deprecated -I$RJI -I$RJI/linux     appelMV.cc
   # liaison
   # répertoire bibliothèque java (3 bib: libjava libverify libjvm)
   export RBJ=${RJH}jre/lib/i386
   g++  appelMV.o -L $RBJ -ljava -lverify -L $RBJ/client -ljvm
   # exécution
   export LD_LIBRARY_PATH=${RJH}jre/lib/i386:${RJH}jre/lib/i386/client
   a.out

*/

#include <jni.h>
#include <stdlib.h>
#ifdef _WIN32
#define SEP_REPERTOIRE ';'    // séparateur de répertoires
#else /* UNIX */
#define SEP_REPERTOIRE ':'
#endif

#define REP_MES_CLASSES "."   // répertoire des classes à charger

int main(int nm, char * tm[]) {
JNIEnv *penv;
JavaVM *jvm;
JDK1_1InitArgs vm_args;
jint res;
jclass i_cls;               // identité de la classe
jmethodID i_mth;            // identité de la méthode
jstring j_ch;               // pour créer une chaîne 'java'
jobjectArray j_to;          // pour tableau d'objets 'java'
char repertoires[2048];


// IMPORTANT: pour version 1.1
vm_args.version = 0x00010001;
// pour version 1.2
vm_args.version = JNI_VERSION_1_2;

JNI_GetDefaultJavaVMInitArgs(&vm_args);

/* Pour charger les classes, ajouter REP_MES_CLASSES après CLASSPATH */
sprintf(repertoires, "%s%c%s",
          vm_args.classpath, SEP_REPERTOIRE, REP_MES_CLASSES);
vm_args.classpath = repertoires;
// Si options, pour la MV, récupérées de la ligne de commande
// vm_args.options = options;
// vm_args.nOptions = dwJLen;
// vm_args.ignoreUnrecognized = TRUE;

/* Créer la machine virtuelle Java */
// res = JNI_CreateJavaVM(&jvm,&penv,&vm_args);
res = JNI_CreateJavaVM(&jvm,(void **)&penv,&vm_args);
if (res < 0) {
fprintf(stderr, "Mchine virtuelle non démarée !!!\n");
exit(1);
}

// Récupérer la classe
i_cls = penv->FindClass("AppelMV");
if (i_cls == 0) {
   fprintf(stderr, "Classe AppelMV non trouvée\n");
   exit(2);
   }

// Récupérer la méthode 'static void main(String [])'
i_mth = penv->GetStaticMethodID(i_cls, "main", "([Ljava/lang/String;)V");
if (i_mth == 0) {
   fprintf(stderr, "Méthode AppelMV.main non trouvée\n");
   exit(3);
   }

// Créer le paramètre de la méthode 'static void main(String [])'
j_ch = penv->NewStringUTF(" depuis C++ !");
if (j_ch == 0) {
   fprintf(stderr, "Plus de mémoire !!!\n");
   exit(4);
   }
j_to = penv->NewObjectArray(1, penv->FindClass("java/lang/String"), j_ch);
if (j_to == 0) {
   fprintf(stderr, "Plus de mémoire !!!\n");
   exit(1);
   }
// Enfin ... l'appel
penv->CallStaticVoidMethod(i_cls, i_mth, j_to);

jvm->DestroyJavaVM();
}

/*
Toutes les méthodes appartiennent à la classe JNIEnv
Utiliser un champ d'un objet
   Le champ est défini, dans la classe, par son nom; la signature du champ
   permet des vérifications. Pour accéder à la valeur, il faut référencer
   l'objet concerné.
   identité du champ:
      jfieldID GetFieldID(jclass classe,char nom[],char signature[])
   valeur du champ (récupération ou modification):
      j<BO> GetObjectField(jobject obj, jfieldID i_champ)
      void SetObjectField(jobject obj, jfieldID i_champ, jobject val)
   
Utilisation d'une méthode
   Un méthode est définie par son nom et sa signature (dans le cas de
   surcharge la signature permet une identifdication unique). Une fois
   la méthode identifiée, dans la classe, on peut l'appeler.
   identité de la méthode:
      jmethodID GetStaticMethodID(jclass classe, const char nom[],
				   const char signature[])
   appels 
      jobject Call<BOV>Method(jobject obj, jmethodID i_methode, ...)
      jobject Call<BOV>MethodV(jobject obj, jmethodID i_methodID, va_list args)
      jobject Call<BOV>MethodA(jobject obj, jmethodID i_methodID, jvalue targ[])

Utilisation d'une chaîne de caractères: jstring
   Deux codages en C: Unicode ou UTF-8; le type char se raproche de UTF-8;
   en C++ on aura deux longueurs (en tant qu'unicode' ou 'UTF-8') et deux
   tableaux accessibles( type jchar[] ou char[]).
      (cf http://www.uzine.net/article1785.html) 
   création d'une chaîne java, à partir d'un tableau de caractères
      ! jstring NewString(const jchar *unicode, jsize longueur)
      ! jstring NewStringUTF(char tc[])
   utilisation
      // Chaînes unicode
      jsize GetStringLength(jstring ch_java)
      const jchar* GetStringChars(jstring ch_java, jboolean *isCopy)
	  // Chaînes utf-8
      jsize GetStringUTFLength(jstring ch_java) {
      const char* GetStringUTFChars(jstring ch_java, jboolean *isCopy)
   gestion mémoire
      void ReleaseStringChars(jstring ch_java, const jchar *chars)
      void ReleaseStringUTFChars(jstring ch_java, const char tc[])

Utilisation d'un tableau: jarray
  Les tableaux envisagés ont des éléments d'un type primitif ou
  du type objet:
     jboolean, jbyte, jchar, jshort int jlong, jfloat, jdouble, jobjet
  Un tableau java est représenté par "un descripteur" contenant
  entre autre sa taille et l'adresse de la zone mémoire où sont
  placés les éléments.
  Ne pas confondre les types des descripteurs:
     jbooleanArray,Array jbyteArray ... jobjetArray
  et les types des  tableaux d'éléments en C++, qui sont respectivement
     jboolean *, jbyte * ... jobjet *
  L'utilisation des tableaux d'objets est plus limitée que pour les
  types primitifs; on commence par exposer les méthodes sur ces types.
 


  création d'un tableau au sens java:
     jintArray NewIntArray(jsize taille)
     idem pour jboolean, jbyte ... jdouble
  accès à la taille, à l'adresse, à un sous-tableau
     jsize GetArrayLength(jarray array)
     jint  * GetIntArrayElements(jintArray t_java, jboolean *isCopy)
     void GetIntArrayRegion(jintArray t_java, jsize debut, jsize longueur,
                            jint sousTableau[])
     void SetIntArrayRegion(jintArray t_java, jsize debut, jsize longueur,
                jint sousTableau[]) {
     idem pour jboolean, jbyte ... jdouble
  gestion mémoire java et C++ et recopie
     void ReleaseIntArrayElements(jintArray t_j, jint t[], jint mode)
     idem pour jboolean, jbyte ... jdouble
  cas des tableaux d'objets
     jobjectArray    NewObjectArray(jsize taille)
     jobject GetObjectArrayElement(jobjectArray t_j, jsize index)
     void SetObjectArrayElement(jobjectArray t_j, jsize index, jobject val)

*/
 

Résumé des commandes

Il faut chercher le répertoire de la distribution java contenant le fichier jni.h.

Dans les commandes ci-dessous on dénomme Truc.java un source java définissant une classe, truc.h les déclarations associées en langage C++ et truc.C le fichier contenant le langage C++ codant les méthodes appelées depuis 'java'.
On a considéré que la bibliothèque partagée, fonctionnant avec la machine virtuelle java, était dans le sous-répertoire lib du répertoire courant.

 

 

 


Bibliothèque JNI

Récapitulation

L'utilisation, en langage C, d'une information codée dans la machine virtuelle Java est plus ou moins directe suivant sa complexité. Les types et les fonctions en C de l'interface JNI, qui permettent cette utilisation, sont rappelées ci-après.

Le cas simple correspond à l'un des 8 types basiques du langage Java: boolean, byte, char, short, int, long, float et double. le type int est pris comme exemple, mais la transposition aux autres types est évidente.
Les cas suivants correspondent aux tableaux d'éléments de type basique et aux chaînes de la classe String de Java, manipulés par l'intermédiaire de descripteurs.
Enfin le cas d'utilisation des champs et méthodes d'une classe Java quelconque, et le cas des tableaux d'objets sont les plus complexes.

Voici un résumé des mots à utiliser avec JNI, suivant l'information à traiter.

  1. Types basiques
    La valeur d'un élément Java de type int est directement récupéré et modifié par la variable correspondante en C, de type jint.
  2. Types tableaux d'éléments basiques
    A partir du descipteur de type jintArray la fonction GetArrayLength donne la taille du tableau, et la fonction GetIntArrayElements fournit l'adresse, de type jint* du premier octet du tableau en C.
    La création d'un tableau en Java, à partir d'un tableau jint* est effectuée par GetIntArrayElements ; avant de retourner à la machine virtuelle Java, un appel à ReleaseIntArrayElements libère la mémoire et obéit à la gestion du "ramasse-miettes" de la machine virtuelle Java.
  3. Objet de la classe chaîne java.lang.String
    A partir du descripteur de type jstring, la fonction GetStringUTFChars donne l'adresse du premier octet des caractères utf-8 de la chîne, alors que la fonction GetStringUTFLength en donne le nombre d'octets.
    GetStringLength fournit la taille de la chaîne
  4. Objet d'une classe quelconque: méthodes et champs
    A partir de l'objet et de la signature du champ, on accède à son identification en C, de type jfieldID par la fonction GetFieldID; puis suivant la nature du champ:
          la fonction GetIntField ou SetIntField accède au champ
          la fonction GetObjectField fournit un descripteur de tableau ou de chaîne ou un objet d'une classe quelconque.
  5. Tableau d'objets
    A partir du descipteur de type jobjectArray la fonction GetArrayLength donne la taille du tableau, et la fonction GetObjectArrayElements fournit l'adresse, de type jobject* du tableau en C.

The JavaTM Tutorial
Les tables suivantes, extraites du site Sun, fournissent les mots définis par la bibliothèque JNI

Mapping Between JNI and Native Types

The following two tables summarize how Java types map to native types and the rules for encoding Java types in native signatures.

Primitive Types and Native Equivalents

Java Type Native TypeSize in bits
boolean jboolean8, unsigned
byte jbyte8
char jchar16, unsigned
short jshort16
int jint32
long jlong64
float jfloat32
double jdouble64
void voidn/a

Encoding for Java Type Signatures

Signature Java Programming Language Type
Z boolean
B byte
C char
S short
I int
J long
F float
D double
L fully-qualified-class ; fully-qualified-class
[ type type[]
( arg-types ) ret-type method type

JNI String Handling Functions

JNI Array Handling Functions

JNI Method Handling Functions

JNI Member Variable Handling Functions

JNI Exception Handling Functions

JNI Local and Global Reference Handling Functions

JNI Thread Synchronization Functions


References