|
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.
| Signature | Type 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.

|