-3-

    II. Possibilités offertes par le JNI


      3. Interfacer des objets Java



Dans le paragraphe précédent nous avons passé une String à la méthode native mais on peut également lui passer des objets Java. A l'intérieur de la méthode native, on peut accéder aux champs et aux méthodes de l'objet reçu.

Pour passer des objets, on commence d’abord par déclarer la méthode native avec la syntaxe Java standard. L'exemple ci-dessous présente la déclaration d’une classe MyJavaClass qui possède un champ public et une méthode public. La classe UseObjects déclare une méthode native qui prend un objet de la classe MyJavaClass. Pour voir si la méthode native manipule son argument, le champ public de l'argument est positionné, la méthode native est appelée, et enfin la valeur du champ public est imprimée.

Exemple : Passage d’un objet Java vers du code C
//----------------PARTIE JAVA-----------------//
class MyJavaClass {
public int aValue;
public void divByTwo() { aValue /= 2; }
public class UseObjects {
private native void changeObject(MyJavaClass obj);
static { System.loadLibrary("UseObjImpl"); }
public static void main(String[] args) {
UseObjects app = new UseObjects();
MyJavaClass anObj = new MyJavaClass();
anObj.aValue = 2;
app.changeObject(anObj);
System.out.println("Java: " + anObj.aValue);
}
} //---------------PARTIE C--------------------// #include <jni.h>
extern "C" JNIEXPORT void JNICALL Java_UseObjects_changeObject( JNIEnv* env, jobject, jobject obj) {
jclass clas = env->GetObjectClass(obj);
jfieldID fid = env->GetFieldID(clas, "aValue", "I");
jmethodID mid = env->GetMethodID(clas, "divByTwo", "()V"); int value = env->GetIntField(obj, fid);
printf("Native: %d\n", value);
env->SetIntField(obj, fid, 6);
env->CallVoidMethod(obj, mid);
value = env->GetIntField(obj, fid);
printf("Native: %d\n", value);
}
Explication du programme :

Ignorant l'équivalent de "this", la fonction C reçoit un jobject, qui est l'aspect natif de la référence à l'objet Java que nous passons depuis le code Java. Nous lisons simplement aValue, l'imprimons, changeons la valeur, appelons la méthode divByTwo() de l'objet, et imprimons la valeur à nouveau.

Si on exécute le programme Java, on verra que l'objet qui est passé depuis le côté Java est manipulé par notre méthode native et le résultat final est :

Native: 2
Native: 3
Java : 3

 

Pour accéder à un champ ou une méthode Java, on doit d'abord obtenir son identificateur en utilisant GetFieldID( ) pour les champs et GetMethodID( ) pour les méthodes. Ces fonctions prennent la classe, une chaîne contenant le nom de l'élément, et une chaîne donnant le type de l'information : le type de donnée du champ, ou l'information de signature d'une méthode (contenu dans le tableau situé à la fin de ce paragraphe). Ces fonctions retournent un identificateur, utilisé pour accéder à l'élément.

SignatureType Java correspondant
V void
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

Tableau des signatures

Pour le type de signature associé à une méthode, les données fournies en paramètres d'entrée sont spécifiées à l'intérieur de parenthèses.

Exemple :
  • int toto(float a, int b) —> (FI)I
  • void titi(int a, int b, int c) —> (III)V

  • Cette approche peut sembler déroutante, mais notre méthode n'a aucune connaissance de la disposition interne de l'objet Java. Au lieu de cela, il doit accéder aux champs et méthodes à travers les index renvoyés par la JVM. Ceci permet aux diverses JVMs d'implémenter différentes dispositions des objets sans impact sur les méthodes natives.

    Le ramasse-miettes travaille pendant l'exécution de la méthode native, mais il est garanti que les objets ne sont pas réclamés par le ramasse-miettes durant l'appel à une méthode native. Pour assurer ceci, des références locales sont créées auparavant, et détruites juste après l'appel à la méthode native. Puisque leur durée de vie englobe l'appel, les objets seront valables pendant la durée d'exécution de la méthode native.

    Comme ces références sont créées et ensuite détruites à chaque fois que la fonction est appelée, on ne peut pas faire des copies locales dans les méthodes natives, dans des variables static. Si on veut une référence qui dure le temps des appels de fonctions, on doit employer une référence globale. Les références globales ne sont pas créées par la JVM, mais le programmeur peut créer une référence globale à partir d'une locale en appelant des fonctions JNI spécifiques. Lorsqu'on crée une référence globale, on devient responsable de la durée de vie de l'objet référencé. La référence globale (et l'objet qu'il référence) sera en mémoire jusqu'à ce que le programmeur libère explicitement la référence avec la fonction JNI appropriée.


    Rédigé par Cyril MOQUEREAU