 |
 |
B) L'Interface Java Natif (JNI) |
|
 |
|
Texte original |
 |
Traducteur : Phillipe BOITE |
|
 |
|
Ce chapitre contient 1 page
|
|
|
 |
 |
 |
 |
 |
 |
|
 |
05.07.01 - version 1.2 : - Ajout des tags de séparation de pages pour le site (armel). 28.04.01 - version 1.1 : - Nettoyage du code html (titres, paragraphes, tableaux, images, ancres) par Armel. - Enlevé de nombreuses balises de type {font}. - Laissé les {strong} et les {em}. 25.06.2000 - Version 1.0 : - Dernière mise à jour de la version française Traducteur : - Philippe BOITE Texte original : -Thinking in Java, 2nd edition, Revision 10 © 2000 by Bruce Eckel
|
 |
 |
 |
B: The Java Native Interface (JNI)
|
 |
B: L'Interface Native Java [Java Native Interface]
(JNI)
|
 |
 |
 |
The material in this appendix was
contributed by and used with the permission of Andrea Provaglio
(www.AndreaProvaglio.com).
|
 |
Le contenu de cette annexe a été fourni et est utilisé avec la
permission d'Andrea Provaglio (www.AndreaProvaglio.com).
|
 |
 |
 |
The Java language and its standard
API are rich enough to write full-fledged applications. But in some cases you
must call non-Java code; for
example, if you want to access operating-system-specific features,
interface with special hardware
devices, reuse a preexisting, non-Java code base, or implement
time-critical sections of code.
|
 |
Le langage Java et son API standard sont suffisamment riches pour
écrire des applications complètes. Mais dans certains cas on doit appeler du code non-Java ; par
exemple, pour accéder à des fonctionnalités spécifiques du système d'exploitation, s'interfacer
avec des matériels particuliers, réutiliser une base de code non-Java existante, ou implémenter des
parties de codes à contraintes temps réel fortes.
|
 |
 |
 |
Interfacing with non-Java code requires
dedicated support in the compiler and in the Virtual Machine, and additional
tools to map the Java code to the non-Java code. The standard solution for
calling non-Java code that is provided by JavaSoft is called the Java Native
Interface, which will be introduced in this appendix. This is not an
in-depth treatment, and in some cases you’re assumed to have partial
knowledge of the related concepts and techniques.
|
 |
S'interfacer avec du code non-Java nécessite un support dédié dans le
compilateur et dans la machine virtuelle, ainsi que des outils supplémentaires pour associer le
code Java au code non-Java. La solution standard fournie par JavaSoft pour appeler du code non-Java
est appelée la Java Native Interface, qui sera introduite dans cette annexe. Ceci n'est
pas un traitement en profondeur, et dans certains cas vous êtes supposés avoir une connaissance
partielle des concepts et techniques concernés.
|
 |
 |
 |
JNI is a fairly rich programming
interface that allows you to call native methods from a Java application. It was
added in Java 1.1, maintaining a certain degree of compatibility with its Java
1.0 equivalent: the native
method interface (NMI). NMI has design characteristics that make it unsuitable
for adoption across all virtual machines. For this reason, future versions of
the language might no longer support NMI, and it will not be covered
here.
|
 |
JNI est une interface de programmation assez riche qui permet d'appeler des
méthodes natives depuis une application Java. Elle a été ajoutée dans Java 1.1, en maintenant un
certain degré de compatibilité avec son équivalent en Java 1.0 : la native method interface (NMI).
NMI a des caractéristiques de conception qui la rendent impropre à l'utilisation sur certaines
machines virtuelles. Pour cette raison, les versions ultérieures du langage pourraient ne plus
supporter NMI, et elle ne sera pas couverte ici.
|
 |
 |
 |
Currently, JNI is designed to interface
with native methods written only in C or C++. Using JNI,
your native methods can:
|
 |
Actuellement, JNI est conçue pour s'interfacer uniquement avec les méthodes
natives écrites en C ou C++. En utilisant JNI, vos méthodes natives peuvent :
|
 |
 |
 |
- Create, inspect, and update
Java objects (including arrays and
Strings)
- Call
Java methods
- Catch
and throw
exceptions
- Load
classes and obtain class
information
- Perform
run-time type checking
|
 |
- créer, inspecter et modifier des objets Java (y compris les tableaux et
les Strings),
- appeler des méthodes Java,
- intercepter [catch] et générer [throw] des
exceptions,
- charger des classes et obtenir des informations sur les
classes,
- effectuer des contrôles lors de l'exécution (run-time type
checking).
|
 |
 |
 |
Thus,
virtually everything you can do with classes and objects in ordinary Java you
can also do in native methods.
|
 |
De ce fait, pratiquement tout ce qu'on peut faire avec des classes et des
objets en Java ordinaire, on peut aussi le faire dans des méthodes natives.
|
 |
 |
 |
Calling a native method
|
 |
Appeler une méthode native
|
 |
 |
 |
We’ll start with a simple example:
a Java program that calls a native method, which in turn calls the standard C
library function printf( ).
|
 |
Nous allons commencer avec un exemple simple : un programme Java qui
appelle une méthode native, qui à son tour appelle la fonction printf( ) de la
bibliothèque C standard.
|
 |
 |
 |
The first step is to write the Java code
declaring a native method and its arguments:
|
 |
La première opération consiste à écrire le code Java qui déclare une
méthode native et ses arguments :
|
 |
 |
 |
//: appendixb:ShowMessage.java public class ShowMessage { private native void ShowMessage(String msg); static { System.loadLibrary("MsgImpl"); // Linux hack, if you can't get your library // path set in your environment: // System.load( // "/home/bruce/tij2/appendixb/MsgImpl.so"); } public static void main(String[] args) { ShowMessage app = new ShowMessage(); app.ShowMessage("Generated with JNI"); } } ///:~
|
 |
//: appendixb:ShowMessage.java public class ShowMessage { privatenative void ShowMessage(String msg); static { System.loadLibrary("MsgImpl"); // Astuce Linux, si vous ne pouvez pas // positionner le chemin des bibliothèques // dans votre environnement : // System.load( //"/home/bruce/tij2/appendixb/UseObjImpl.so"); } public static void main(String[] args) { ShowMessage app = new ShowMessage(); app.ShowMessage("Generated with JNI"); } } ///:~
|
 |
 |
 |
The native method declaration is followed
by a static block that calls System.loadLibrary( ) (which you
could call at any time, but this style is more appropriate).
System.loadLibrary( ) loads a DLL in memory and links to it. The DLL
must be in your system library path. The file name extension is automatically
added by the JVM depending on the platform.
|
 |
La déclaration de la méthode native est suivie par un bloc
static qui appelle System.loadLibrary( ) (qu'on pourrait
appeler à n'importe quel moment, mais ce style est plus clair).
System.loadLibrary( ) charge une DLL en mémoire et se lie à elle. La DLL doit
être dans votre chemin d'accès aux bibliothèques système (system library path).
L'extension de nom de fichier est automatiquement ajoutée par la JVM dépendant de la
plateforme.
|
 |
 |
 |
In the above code you can also see a call
to the System.load( ) method, which is commented out. The path
specified here is an absolute path, rather than relying on an environment
variable. Using an environment variable is naturally the better and more
portable solution, but if you can’t figure that out you can comment out
the loadLibrary( ) call and uncomment this one, adjusting the path
to your own directory.
|
 |
Dans le code ci-dessus on peut voir également un appel à la méthode
System.load( ) , qui est mis en commentaire. Le chemin spécifié ici est un
chemin absolu, plutôt que dépendant d'une variable d'environnement. Utiliser une variable
d'environnement est naturellement une meilleure solution et est plus portable, mais si vous n'y
arrivez pas vous pouvez mettre en commentaire l'appel à loadLibrary( ) et
enlever la mise en commentaire de l'appel à la méthode System.load(), en ajustant le chemin de
votre propre répertoire.
|
 |
 |
 |
The header file generator: javah
|
 |
Le générateur d'entête [ header file generator] :
javah
|
 |
 |
 |
Now compile your Java source file and run
javah on the resulting .class file,
specifying the —jni switch (this is done automatically for you by
the makefile in the source code distribution for this book):
|
 |
Maintenant, compilez votre fichier source Java et lancez
javah sur le fichier .class résultant, en précisant le paramètre
-jni (ceci est déjà fait dans le makefile inclus dans la fourniture du code source
pour ce livre) :
|
 |
 |
 |
javah —jni ShowMessage
|
 |
javah -jni ShowMessage
|
 |
 |
 |
javah reads the Java class file and for each native method declaration it generates a function prototype in a C or C++ header file. Here’s the output: the ShowMessage.h source file (edited slightly to fit into this book):
|
 |
javahlit la classe Java et pour chaque déclaration de
méthode native il génère un prototype de fonction dans un fichier d'entête C ou C++. Voici le
résultat : le fichier source ShowMessage.h (légèrement modifié pour l'inclure dans
ce livre) :
|
 |
 |
 |
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class ShowMessage */
#ifndef _Included_ShowMessage #define _Included_ShowMessage #ifdef __cplusplus extern "C" { #endif /* * Class: ShowMessage * Method: ShowMessage * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_ShowMessage_ShowMessage (JNIEnv *, jobject, jstring);
#ifdef __cplusplus } #endif #endif
|
 |
/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class ShowMessage */
#ifndef _Included_ShowMessage #define _Included_ShowMessage #ifdef __cplusplus extern "C" { #endif /* * Class: ShowMessage * Method: ShowMessage * Signature: (Ljava/lang/String;)V */ JNIEXPORT void JNICALL Java_ShowMessage_ShowMessage (JNIEnv *, jobject, jstring);
#ifdef __cplusplus } #endif #endif
|
 |
 |
 |
As you can see by the #ifdef
__cplusplus preprocessor directive, this file can be compiled either by a C
or a C++ compiler. The first #include directive includes jni.h, a
header file that, among other things, defines the types that you can see used in
the rest of the file. JNIEXPORT and
JNICALL are macros that expand to match
platform-specific directives. JNIEnv, jobject and jstring
are JNI data type definitions, which will be explained
shortly.
|
 |
Comme on peut le voir par la directive préprocesseur #ifdef
__cplusplus , ce fichier peut être compilé aussi bien par un compilateur C que C++. La
première directive #include inclut jni.h, un fichier d'entête
qui, entre autres choses, définit les types qu'on peut voir utilisés dans le reste du fichier.
JNIEXPORT et JNICALL sont des macros qui une fois étendues
génèrent les directives spécifiques aux différentes plateformes. JNIEnv,
jobject et jstring sont des définitions de types de données, qui
seront expliquées par la suite.
|
 |
 |
 |
Name mangling and function signatures
|
 |
Les conventions de nommage [name mangling]et les signatures de
fonctions
|
 |
 |
 |
JNI imposes a naming convention (called
name mangling) on native methods. This is important, since it’s
part of the mechanism by which the virtual machine links Java calls to native
methods. Basically, all native methods start with the word “Java,”
followed by the name of the class in which the Java native declaration appears,
followed by the name of the Java method. The underscore character is used as a
separator. If the Java native method is overloaded, then the function signature
is appended to the name as well; you can see the native signature in the
comments preceding the prototype. For more information about name mangling and
native method signatures, please refer to the JNI
documentation.
|
 |
JNI impose une convention de nommage (appelée name mangling) aux
méthodes natives. Ceci est important car cela fait partie du mécanisme par lequel la machine
virtuelle lie les appels Java aux méthodes natives. Fondamentalement, toutes les méthodes natives
commencent par le mot "Java", suivi par le nom de la méthode native. Le caractère sous-tiret est
utilisé comme séparateur. Si la méthode java native est surchargée, alors la signature de fonction
est ajoutée au nom également ; on peut voir la signature native dans les commentaires précédant le
prototype. Pour plus d'informations sur les conventions de nommage, se référer à la documentation
de JNI.
|
 |
 |
 |
Implementing your DLL
|
 |
Implémenter votre DLL
|
 |
 |
 |
At this point, all you have to do is
write a C or C++ source code file that includes the javah-generated
header file and implements the native method, then compile it and generate a
dynamic link library. This part is platform-dependent. The code below is
compiled and linked into a file called MsgImpl.dll for Windows or
MsgImpl.so for Unix/Linux (the makefile packaged with the code listings
contains the commands to do this—it is available on the CD ROM bound into
this book, or as a free download from
www.BruceEckel.com):
|
 |
Arrivé ici, il ne reste plus qu'à écrire un fichier source C ou C++ qui
inclut le fichier d'entête généré par javah et implémenter la méthode native, puis
le compiler et générer une bibliothèque dynamique. Cette opération dépend de la plateforme. Le code
ci-dessous est compilé et lié dans un fichier appelé MsgImpl.dll pour Windows ou
MsgImpl.so pourUnix/Linux (le makefile fourni avec les listings du code contient
les commandes pour faire ceci ; il est disponible sur le CD ROM fourni avec ce livre, ou en
téléchargement libre sur www.BruceEckel.com) :
|
 |
 |
 |
//: appendixb:MsgImpl.cpp //# Tested with VC++ & BC++. Include path must //# be adjusted to find the JNI headers. See //# the makefile for this chapter (in the //# downloadable source code) for an example. #include <jni.h> #include <stdio.h> #include "ShowMessage.h"
extern "C" JNIEXPORT void JNICALL Java_ShowMessage_ShowMessage(JNIEnv* env, jobject, jstring jMsg) { const char* msg=env->GetStringUTFChars(jMsg,0); printf("Thinking in Java, JNI: %s\n", msg); env->ReleaseStringUTFChars(jMsg, msg); } ///:~
|
 |
//: appendixb:MsgImpl.cpp //# Testé avec VC++ & BC++. Le chemin d'include //# doit être adapté pour trouver les en-têtes //# JNI. Voir le makefile de ce chapitre //# (dans le code source téléchargeable) //# pour exemple. #include <jni.h> #include <stdio.h> #include "ShowMessage.h"
extern "C" JNIEXPORT> void JNICALL Java_ShowMessage_ShowMessage(JNIEnv* env, jobject, jstring jMsg) { const char* msg=env->GetStringUTFChars(jMsg,0); printf("Thinking in Java, JNI: %s\n", msg); env->ReleaseStringUTFChars(jMsg, msg); ///:~
|
 |
 |
 |
The arguments that are passed into the
native method are the gateway back into Java. The first, of type
JNIEnv, contains all the hooks that allow you to call back into
the JVM. (We’ll look at this in the next section.) The second argument has
a different meaning depending on the type of method. For non-static
methods like the example above, the second argument is the equivalent of the
“this” pointer in C++ and similar to this in Java: it’s
a reference to the object that called the native method. For static
methods, it’s a reference to the Class object where the method is
implemented.
|
 |
Les arguments qui sont passés à la méthode native permettent de revenir
dans Java. Le premier, de type JNIEnv, contient tous les points d'ancrage
[hooks] (nous allons revenir là-dessus dans la section suivante). Le deuxième argument a
une signification différente selon le type de la méthode. Pour des méthodes
non-static comme dans l'exemple ci-dessus, le deuxième argument est l'équivalent
du pointeur "this" en C++ et similaire au this en Java : c'est une référence à
l'objet qui a appelé la méthode native. Pour les méthodes static, c'est une
référence à l'objet Class où la méthode est implémentée.
|
 |
 |
 |
The remaining arguments represent the
Java objects passed into the native method call. Primitives are also passed in
this way, but they come in by value.
|
 |
Les arguments restants représentent les objets java passés dans l'appel de
la méthode native. Les scalaires sont aussi passés de cette façon, mais en les passant par
valeur.
|
 |
 |
 |
In the following sections we’ll
explain this code by looking at the ways that you access and control the JVM
from inside a native method.
|
 |
Dans les sections suivantes nous allons expliquer ce code en regardant de
quelle façon on accède à la JVM et comment on la contrôle depuis l'intérieur d'une méthode
native.
|
 |
 |
 |
Accessing JNI functions: the JNIEnv
argument
|
 |
Accéder à des fonctions JNI : l'argument JNIEnv
|
 |
 |
 |
JNI functions are those that the
programmer uses to interact with the JVM from inside a native method. As you can
see in the example above, every JNI native method receives a special argument as
its first parameter: the JNIEnv argument, which is a pointer to a special
JNI data structure of type JNIEnv_. One element of the JNI data structure
is a pointer to an array generated by the JVM. Each element of this array is a
pointer to a JNI function. The JNI functions can be called from the native
method by dereferencing these pointers (it’s simpler than it sounds).
Every JVM provides its own implementation of the JNI functions, but their
addresses will always be at predefined offsets.
|
 |
Les programmeurs utilisent les fonctions JNI pour interagir avec la JVM
depuis une méthode native. Comme on l'a vu dans l'exemple ci-dessus, chaque méthode native JNI
reçoit un argument spécial comme premier paramètre : l'argument JNIEnv, qui est un pointeur vers
une structure de données JNI spéciale de type JNIEnv_. Un élément de la structure
de données JNI est un pointeur vers un tableau généré par la JVM. Chaque élément de ce tableau est
un pointeur vers une fonction JNI. Les fonctions JNI peuvent être appelée depuis la méthode native
en déréférençant ces pointeurs (c'est plus simple qu'il n'y paraît). Chaque JVM fournit sa propre
implémentation des fonctions JNI, mais leurs adresses seront toujours à des décalages
prédéfinis.
|
 |
 |
 |
Through the JNIEnv argument, the
programmer has access to a large set of functions. These functions can be
grouped into the following categories:
- Obtaining version
information
- Performing
class and object
operations
- Handling
global and local references to Java
objects
- Accessing
instance fields and static
fields
- Calling
instance methods and static
methods
- Performing
string and array
operations
- Generating
and handling Java
exceptions
|
 |
A l'aide de l'argument JNIEnv, le programmeur a accès à un
large éventail de fonctions. Ces fonctions peuvent être regoupées selon les catégories suivantes
:
- obtenir une information de version,
- effectuer des opérations sur les classes et les objets,
- gérer des références globales et locales à des objets Java,
- accéder à des champs d'instances et à des champs statiques [instance
fields and static fields],
- appeler des méthodes d'instances et des méthodes statiques,
- effectuer des opérations sur des chaînes et des tableaux,
- générer et gérer des exceptions Java.
|
 |
 |
 |
The number of JNI
functions is quite large and won’t be covered here. Instead, I’ll
show the rationale behind the use of these functions. For more detailed
information, consult your compiler’s JNI documentation.
|
 |
Il existe de nombreuses fonctions JNI qui ne seront pas couvertes ici. A la
place, je vais montrer le principe d'utilisation de ces fonctions. Pour des informations plus
détaillées, consultez la documentation JNI de votre compilateur.
|
 |
 |
 |
If you take a look at the jni.h
header file, you’ll see that inside the #ifdef __cplusplus
preprocessor conditional, the JNIEnv_ structure is defined as a class
when compiled by a C++ compiler. This class contains a number of inline
functions that let you access the JNI functions with an easy and familiar
syntax. For example, the line of C++ code in the preceding
example:
|
 |
Si on jette un coup d'oeil au fichier d'entête jni.h , on
voit qu'à l'intérieur de la directive préprocesseur #ifdef __cplusplus, la
structure JNIEnv_ est définie comme une classe quand elle est compilée par un
compilateur C++. Cette classe contient un certain nombre de fonctions inline qui permettent
d'accéder aux fonctions JNI à l'aide d'une syntaxe simple et familière. Par exemple, la ligne de
code C++ de l'exemple précédent :
|
 |
 |
 |
env->ReleaseStringUTFChars(jMsg, msg);
|
 |
env->ReleaseStringUTFChars(jMsg, msg);
|
 |
 |
 |
could also be called from C like this:
|
 |
pourrait également être appelée en C comme ceci :
|
 |
 |
 |
(*env)->ReleaseStringUTFChars(env, jMsg, msg);
|
 |
(*env)->ReleaseStringUTFChars(env, jMsg, msg);
|
 |
 |
 |
You’ll notice that the C style is (naturally) more complicated—you need a double dereferencing of the env pointer, and you must also pass the same pointer as the first parameter to the JNI function call. The examples in this appendix use the C++ style.
|
 |
On notera que le style C est (naturellement) plus compliqué ; on doit
utiliser un double déréférencement du pointeur env, et on doit aussi passer ce
même pointeur comme premier paramètre de l'appel à la fonction JNI. Les exemples de cette annexe
utilisent le style C++.
|
 |
 |
 |
Accessing Java Strings
|
 |
Accéder à des chaînes Java
|
 |
 |
 |
As an example of accessing a JNI function, consider the code in MsgImpl.cpp. Here, the JNIEnv argument env is used to access a Java String. Java Strings are in Unicode format, so if you receive one and want to pass it to a non-Unicode function (printf( ), for example), you must first convert it into ASCII characters with the JNI function GetStringUTFChars( ). This function takes a Java String and converts it to UTF-8 characters. (These are 8 bits wide to hold ASCII values or 16 bits wide to hold Unicode. If the content of the original string was composed only of ASCII, the resulting string will be ASCII as well.)
|
 |
Comme exemple d'accès à une fonction JNI, observez le code dans
MsgImpl.cpp. Ici, l'argument JNIEnv env est
utilisé pour accéder à une String Java. Les Strings Java sont au
format Unicode, donc si on en reçoit une et qu'on veut la passer à une fonction non-Unicode
(printf( ), par exemple), il faut d'abord la convertir en caractères ASCII
avec la fonction JNI GetStringUTFChars( ). Cette fonction prend une
String Java et la convertir en caractères UTF-8 (ceux-ci font 8 bits pour contenir
de valeurs ASCII ou 16 bits pour contenir de l'Unicode ; si le contenu de la chaîne d'origine était
composée uniquement d'ASCII, la chaîne résultante sera également de l'ASCII).
|
 |
 |
 |
GetStringUTFChars( ) is one of the member functions in JNIEnv. To access the JNI function, we use the typical C++ syntax for calling a member function though a pointer. You use the form above to access all of the JNI functions.
|
 |
GetStringUTFChars( )> est une des fonctions
membres de JNIEnv. Pour accéder à la fonction JNI, on utilise la syntaxe typique
C++ pour appeler une fonction membre à l'aide d'un pointeur. On utilise la forme ci-dessus pour
appeler l'ensemble des fonctions JNI.
|
 |
 |
 |
Passing and using Java objects
|
 |
Passer et utiliser des objets Java
|
 |
 |
 |
In the previous example we passed a String to the native method. You can also pass Java objects of your own creation to a native method. Inside your native method, you can access the fields and methods of the object that was received.
|
 |
Dans l'exemple précédent nous avons passé une String à la méthode native.
On peut aussi passer des objets Java de sa propre création à une méthode native. A l'intérieur de
sa méthode native, on peut accéder aux champs [fields] et aux méthodes de l'objet
reçu.
|
 |
 |
 |
To pass objects, use the ordinary Java syntax when declaring the native method. In the example below, MyJavaClass has one public field and one public method. The class UseObjects declares a native method that takes an object of class MyJavaClass. To see if the native method manipulates its argument, the public field of the argument is set, the native method is called, and then the value of the public field is printed.
|
 |
Pour passer des objets, on utilise la syntaxe Java normale quand on déclare
la méthode native. Dans l'exemple ci-dessous, MyJavaClass a 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.
|
 |
 |
 |
//: appendixb:UseObjects.java class MyJavaClass { public int aValue; public void divByTwo() { aValue /= 2; } }
public class UseObjects { private native void changeObject(MyJavaClass obj); static { System.loadLibrary("UseObjImpl"); // Linux hack, if you can't get your library // path set in your environment: // System.load( //"/home/bruce/tij2/appendixb/UseObjImpl.so"); } 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); } } ///:~
|
 |
//: appendixb:UseObjects.java class MyJavaClass { public int aValue; public void divByTwo() { aValue /= 2; } } public class UseObjects { private native void changeObject(MyJavaClass obj); static { System.loadLibrary("UseObjImpl"); // Astuce Linux, si vous ne pouvez pas // positionner le chemin des bibliothèques // dans votre environnement : // System.load( //"/home/bruce/tij2/appendixb/UseObjImpl.so"); } 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); } } ///:~
|
 |
 |
 |
After compiling the code and running
javah, you can implement the native method. In the example below, once
the field and method ID are obtained, they are accessed through JNI
functions.
|
 |
Après avoir compilé le code et exécuté javah, on peut
implémenter la méthode native. Dans l'exemple ci-dessous, après avoir obtenu les identificateurs du
champ et de la méthode, on y accède à l'aide de fonctions JNI.
|
 |
 |
 |
//: appendixb:UseObjImpl.cpp //# Tested with VC++ & BC++. Include path must //# be adjusted to find the JNI headers. See //# the makefile for this chapter (in the //# downloadable source code) for an example. #include <jni.h> extern "C" JNIEXPORT void JNICALL Java_UseObjects_changeObject( JNIEnv* env, jobject, jobject obj) { jclass cls = env->GetObjectClass(obj); jfieldID fid = env->GetFieldID( cls, "aValue", "I"); jmethodID mid = env->GetMethodID( cls, "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); } ///:~
|
 |
//: appendixb:UseObjImpl.cpp //# Testé avec VC++ & BC++. Le chemin d'include //# doit être adapté pour trouver les en-têtes //# JNI. Voir le makefile de ce chapitre //# (dans le code source téléchargeable) //# pour exemple.
#include <jni.h> extern "C" JNIEXPORT void JNICALL Java_UseObjects_changeObject( JNIEnv* env, jobject, jobject obj) { jclass cls = env->GetObjectClass(obj); jfieldID fid = env->GetFieldID( cls, "aValue", "I"); jmethodID mid = env->GetMethodID( cls, "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); } ///:~
|
 |
 |
 |
Ignoring the “this”
equivalent, the C++ function receives a jobject, which is the native side
of the Java object reference we pass from the Java code. We simply read
aValue, print it out, change the value, call the object’s
divByTwo( ) method, and print the value out again.
|
 |
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.
|
 |
 |
 |
To access a Java field or method, you
must first obtain its identifier using GetFieldID( ) for fields and
GetMethodID( ) for methods. These functions take the class object, a
string containing the element name, and a string that gives type information:
the data type of the field, or signature information for a method (details can
be found in the JNI documentation). These functions return an identifier that
you use to access the element. This approach might seem convoluted, but your
native method has no knowledge of the internal layout of the Java object.
Instead, it must access fields and methods through indexes returned by the JVM.
This allows different JVMs to implement different internal object layouts with
no impact on your native methods.
|
 |
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 (des détails peuvent être trouvés dans
la documentation de JNI). Ces fonctions retournent un identificateur qu'on doit utiliser pour
accéder à l'élément. Cette approche pourrait paraître tordue, 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 à différentes JVMs d'implémenter
différentes dispositions des objets sans impact sur vos méthodes natives.
|
 |
 |
 |
If you run the Java program, you’ll
see that the object that’s passed from the Java side is manipulated by
your native method. But what exactly is passed? A pointer or a Java reference?
And what is the garbage collector doing during native method
calls?
|
 |
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. Mais qu'est ce qui est exactement passé ? Un
pointeur ou une référence Java? Et que fait le ramasse-miettes pendant l'appel à des méthodes
natives ?
|
 |
 |
 |
The garbage
collector continues to operate during native method execution, but it’s
guaranteed that your objects will not be garbage-collected during a native
method call. To ensure this, local references are created before, and
destroyed right after, the native method call. Since their lifetime wraps the
call, you know that the objects will be valid throughout the native method
call.
|
 |
Le ramasse-miettes continue à travailler pendant l'exécution de la méthode
native, mais il est garanti que vos objets ne seront 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, on sait que les objets seront valables tout au long de l'appel à la méthode
native.
|
 |
 |
 |
Since these references are created and
subsequently destroyed every time the function is called, you cannot make local
copies in your native methods, in static variables. If you want a
reference that lasts across function invocations, you need a global reference.
Global references are not created by the JVM, but the programmer can make a
global reference out of a local one by calling specific JNI functions. When you
create a global reference, you become responsible for the lifetime of the
referenced object. The global reference (and the object it refers to) will be in
memory until the programmer explicitly frees the reference with the appropriate
JNI function. It’s similar to malloc( ) and
free( ) in C.
|
 |
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 tout le temps des appels de
fonctions, on a besoin d'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.
C'est similaire à malloc() et free() en C.
|
 |
 |
 |
JNI and Java exceptions
|
 |
JNI et les exceptions Java
|
 |
 |
 |
With JNI, Java
exceptions can be thrown, caught, printed, and rethrown just as they are inside
a Java program. But it’s up to the programmer to call dedicated JNI
functions to deal with exceptions. Here are the JNI functions for exception
handling:
- Throw( )Throws
an existing exception object. Used in native methods to rethrow an
exception.
- ThrowNew( )Generates
a new exception object and throws
it.
- ExceptionOccurred( )Determines
if an exception was thrown and not yet
cleared.
- ExceptionDescribe( )Prints
an exception and the stack
trace.
- ExceptionClear( )Clears
a pending
exception.
- FatalError( )Raises
a fatal error. Does not
return.
|
 |
Avec JNI, les exceptions Java peuvent être générées, interceptées,
imprimées et retransmises exactement comme dans un programme Java. Mais c'est au programmeur
d'appeler des fonctions JNI dédiées pour traiter les exceptions. Voici les fonctions JNI pour gérer
les exceptions :
- Throw( ) Émet un objet exception existant. Utilisé
dans les méthodes natives pour réémettre une exception.
- ThrowNew( ) Génère un nouvel objet exception et
l'émet.
- ExceptionOccurred( ) Détermine si une exception a
été émise et pas encore effacée [cleared].
- ExceptionDescribe( ) Imprime une exception et la
trace de la pile [stack trace].
- ExceptionClear( )> Efface une exception en
cours.
- FatalError( ) Lève [raises] une erreur
fatale. Ne revient pas [does not return].
|
 |
 |
 |
Among these, you
can’t ignore ExceptionOccurred( ) and
ExceptionClear( ). Most JNI functions can generate exceptions, and
there is no language feature that you can use in place of a Java try block, so
you must call ExceptionOccurred( ) after each JNI function call to
see if an exception was thrown. If you detect an exception, you may choose to
handle it (and possibly rethrow it). You must make certain, however, that the
exception is eventually cleared. This can be done in your function using
ExceptionClear( ) or in some other function if the exception is
rethrown, but it must be done.
|
 |
Parmi celles-ci, on ne peut pas ignorer
ExceptionOccurred( ) et ExceptionClear( ). La plupart
des fonctions JNI peuvent générer des exceptions, et comme le langage ne dispose pas de
construction équivalente à un bloc try Java, il faut appeler
ExceptionOccurred( ) après chaque appel à une fonction JNI pour voir si une
exception a été émise. Si on détecte une exception, on peut choisir de la traiter (et peut-être la
réémettre). Il faut cependant s'assurer qu'une exception est effacée en final. Ceci peut être fait
dans notre fonction en utilisant ExceptionClear( ) ou dans une autre fonction
si l'exception est réémise, mais cela doit être fait.
|
 |
 |
 |
You must ensure that the exception is
cleared, because otherwise the results will be unpredictable if you call a JNI
function while an exception is pending. There are few JNI functions that are
safe to call during an exception; among these, of course, are all the exception
handling functions.
|
 |
Il faut s'assurer que l'exception est effacée, sinon les résultats seront
imprévisibles si on appelle une fonction JNI alors qu'une exception est en cours. Il y a peu de
fonctions JNI qu'on peut appeler sans risque durant une exception ; parmi celles-ci, évidemment, il
y a toutes les fonctions de traitement des exceptions.
|
 |
 |
 |
JNI and threading
|
 |
JNI et le threading
|
 |
 |
 |
Since Java is a multithreaded language,
several threads can call a native method concurrently. (The native method might
be suspended in the middle of its operation when a second thread calls it.)
It’s entirely up to the programmer to guarantee that the native call is
thread-safe; i.e., it does not modify shared data in an unmonitored way.
Basically, you have two options: declare the native method as
synchronized, or implement some other strategy within the native method
to ensure correct, concurrent data manipulation.
|
 |
Puisque Java est un langage multithread, plusieurs threads peuvent appeler
une méthode native en même temps (la méthode native peut être suspendue au milieu de son traitement
lorsqu'un second thread l'appelle). C'est au programmeur de garantir que l'appel natif est
"thread-safe" ; c'est à dire qu'il ne modifie pas des données partagées de façon non contrôlée.
Fondamentalement, il y a deux possibilités : déclarer la méthode native
synchronized, ou implémenter une autre stratégie à l'intérieur de la méthode
native pour assurer une manipulation correcte des données concurrentes.
|
 |
 |
 |
Also, you should never pass the
JNIEnv pointer across threads, since the internal structure it points to
is allocated on a per-thread basis and contains information that makes sense
only in that particular thread.
|
 |
De plus, on ne devrait jamais passer le pointeur JNIEnv à travers des
threads, car la structure interne sur laquelle il pointe est allouée thread par thread et contient
des informations qui n'ont de sens que dans ce thread particulier.
|
 |
 |
 |
Using a preexisting code base
|
 |
Utiliser une base de code préexistantes
|
 |
 |
 |
The easiest way to implement JNI native
methods is to start writing native method prototypes in a Java class, compile
that class, and run the .class file through javah. But what if you
have a large, preexisting code base that you want to call from Java? Renaming
all the functions in your DLLs to match the JNI name mangling convention is not
a viable solution. The best approach is to write a wrapper DLL
“outside” your original code base. The Java code calls functions in
this new DLL, which in turn calls your original DLL functions. This solution is
not just a work-around; in most cases you must do this anyway because you must
call JNI functions on the object references before you can use
them.
|
 |
La façon la plus simple d'implémenter des méthodes JNI natives est de
commencer à écrire des prototypes de méthodes natives dans une classe Java, de compiler cette
classe, et exécuter le fichier .class avec javah. Mais si on
possède une large base de code préexistante qu'on désire appeler depuis Java ? Renommer toutes les
fonctions de notre DLL pour les faire correspondre aux conventions du JNI name mangling n'est pas
une solution viable. La meilleure approche est d'écrire une DLL d'encapsulation "à l'extérieur" de
votre base de code d'origine. Le code Java appelle des fonctions de cette nouvelle DLL, qui à son
tour appelle les fonctions de votre DLL d'origine. Cette solution n'est pas uniquement un
contournement ; dans la plupart des cas on doit le faire de toutes façons parce qu'on doit appeler
des fonctions JNI sur les références des objets avant de pouvoir les utiliser.
|
 |
 |
 |
Additional information
|
 |
Information complémentaire
|
 |
 |
 |
You can find further introductory
material, including a C (rather than C++) example and discussion of Microsoft
issues, in Appendix A of the first edition of this book, which can be found on
the CD ROM bound in with this book, or in a free download from
www.BruceEckel.com. More extensive information is available at
java.sun.com (in the search engine, select “training &
tutorials” for keywords “native methods”). Chapter 11 of
Core Java 2, Volume II, by Horstmann & Cornell (Prentice-Hall,
2000) gives excellent coverage of native methods.
|
 |
Vous pouvez trouver des éléments d'introduction, y compris un exemple C
(plutôt que C++) et une discussion des problèmes Microsoft, dans l'Annexe A de la première édition
de ce livre, que vos pouvez trouver sur le CD ROM fourni avec ce livre, ou en téléchargement libre
sur www.BruceEckel.com. Des informations plus
complètes sont disponibles sur java.sun.com (dans le moteur de recherche, sélectionnez "training
& tutorials" avec "native methods" comme mots-clés). Le chapitre 11 de Core Java 2,
Volume II, par Horstmann & Cornell (Prentice-Hall, 2000) donne une excellente
description des méthodes natives.
|
 |
 |
 |
 |
 |
 |
 |
 |
|
 |
 |
 |