Approche de JNI via Delphi
Date de publication : 26 août 2008
Par
Philippe Sénéchal (Accueil)
Ce tutoriel a pour objectif de découvrir l'utilisation de JNI avec Delphi.
I. Avant propos
La portabilité est l'un des avantages souvent avancé dans le choix de Java en tant que langage de programmation.
Toutefois, pour des raisons de communication avec d'autres programmes (ou le système d'exploitation),
on est parfois appelé à développer une passerelle à l'aide d'autres langages. JNI a été intégré à
la JRE dans ce but, mais son utilisation nécessite de maitriser le C ou le C++. Il existe pourtant
une posssibilité sous Windows , d'utiliser JNI en programmant en Delphi, qui possède par ailleurs
de nombreux composants de communication (COM, OLE, DDE, registre).
Cette solution est documentée par Matthew Mead à cette adresse
Using
the Java Native Interface with Delphi , et repose sur l'utilisation de la bibliothèque jni.pas
qui est une interface en Delphi de la Java Native Interface.
Programmant essentiellement en Java (et en C# qui en est assez proche), je me suis intéressé à cette
solution à partir d'un besoin : j'ai programmé un utilitaire permettant de manipuler les données
issues d'un autre programme développé sous Omnis 7.1. Ce dernier manipule un fichier dont les données
sont stockées dans des tables au format Omnis. On peut en java accéder aux données de ces tables
via le driver OdbcJdbc, mais pour savoir quelle est la fiche en cours d'utilisation, il faut utiliser
le canal DDE (Dynamic Data Exchange).
DDE est un protocole client-serveur définit par Microsoft pour l'échange de données entre applications.
DDE est désuet, mais reste utilisable pour beaucoup d'applications développées à l'époque où il était
d'actualité. (Excel, Word, produit développé sous Omnis, 4D, et bien d'autres probablement...). Ne
possédant pas de connaissance en C et C++, je pensais JNI hors de ma portée, jusqu'à ce que je découvre
cette possibilité en Delphi. Je vais donc développer l'abord de JNI avec Delphi à partir de la construction
d'une dll d'accès aux données DDE.
II. Pré-requis
III. Le programme java
J'utilise ici une classe minimaliste, avec une méthode main permettant d'afficher sur la console l'index
de la fiche que l'on va rechercher sur le canal DDE.
La méthode qui permet cette recherche se nomme getNCE() ; elle doit retourner un entier. Elle est
précédée du mot clé 'native' et n'a pas de corps : celui-ci sera programmé dans la dll.
Enfin, il y a un bloc static qui crée le lien avec la dll.
| code Java : 'DDEToJava.java' |
public class DDEToJava {
public static void main(String[] args) {
System.out.println(getNCE());
}
native public static Integer getNCE();
static {
System.loadLibrary("DDEToJava");
}
}
|
IV. Création de la dll avec JavaToDPR
Une fois compilée la classe DDEToJava, on peut utiliser le programme JavaToDPR avec la commande :
| en ligne de commande : |
java JavaToDPR -o DDEToJava.dpr DDEToJava
|
Cette commande va créer un fichier DDEToJava.dpr que l'on va pouvoir ouvrir avec Delphi.
JavaToDPR permet de respecter la syntaxe utilisée dans jni.pas.
Le nom de notre fonction native a comme syntaxe : Java_NomDeLaClasse_NomDeLaMéthode.
Elle a 2 arguments propres :
-
un Objet PJNIEnv qui correspond à un environnement JNI et permet d'accéder à des fonctions qui implémentent
les opérations de base de l'interopérabilité avec Java. Cet environnement JNI contient des informations
sur la zone de mémoire où la machine virtuelle de Java est allouée, le fil d'exécution (thread)
sur lequel l'environnement JNI est créé, et des pointeurs vers les fonctions qui implémentent les
opérations de la JNI.
-
et un Objet JObject équivalent de 'this' en java.
| code Delphi : 'DDEToJava.dpr' |
library DDEToJava;
uses JNI;
function Java_DDEToJava_getNCE (PEnv: PJNIEnv; Obj: JObject ): JInt; stdcall;
begin
end;
exports
Java_DDEToJava_getNCE;
end.
|
V. Implémentation de la fonction en Delphi
Pour implémenter cette fonction, on va avoir besoin du composant 'DdeClientConv' qui appartient à la
bibliothèque 'DdeMan'.
Ce composant représente une conversation DDE avec une application serveur DDE ; il est accessible
à partir de l'onglet 'système' dans la barre des composants de Delphi. Comme on crée une dll sans
fenêtre, on ne va pas se servir du composant visuel, mais on va l'instancier avec la variable 'Nil'.
Puis on spécifie le service et la rubrique de la conversation DDE : ici c'est AXILOG (Le programme
s'appelle Axilog.exe) et AXISANTE (la librairie utilisée par le programme se nomme : Axisante.lbr).
Ce point nécessite de connaitre des données propres aux programmes utilisés.
Et à l'aide de ce lien, on va chercher la valeur souhaitée : ici, c'est la valeur contenue dans 'NCEPatient'
correspondant au nom de la clé primaire de la table traitant les fiches. Une conversion est nécessaire,
car on récupère une chaine, et la fonction retourne un entier.
Ce qui nous donne le listing final :
| code Delphi : 'DDEToJava.dpr' |
library DDEToJava;
uses JNI, DdeMan;
function Java_DDEToJava_getNCE (PEnv: PJNIEnv; Obj: JObject ): JInt; stdcall;
begin
result := 0;
with TDdeClientConv.Create(Nil) do
try
ConnectMode := ddeManual;
SetLink('AXILOG','AXISANTE');
if OpenLink then
result := StrToInt(RequestData('NCEPatient'));
finally
Free;
end;
end;
exports
Java_DDEToJava_getNCE;
end.
|
Pour utiliser cette fonction d'une façon plus générique, il suffit de mettre les 3 variables (String)
utilisées (AXILOG,AXISANTE, NCEPATIENT) en argument de la fonction. Cette approche soulève l'une
des difficultés de l'utilisation de JNI, à savoir la correspondance entre les types de données de
chaque langage (types de base , String ...).
Si Java n'a qu'un format de String, Delphi utilise lui 3 formats différents :
|
Type
|
Longueur maximum
|
Mémoire nécessaire
|
Utilisation
|
|
ShortString
|
255 caractères
|
de 2 à 256 octets
|
Compatibilité ascendante
|
|
AnsiString
|
~2^31 caractères
|
de 4 octets à 2Go
|
Caractères sur 8 bits (ANSI), DBCS ANSI, MBCS ANSI, etc
|
|
WideString
|
~2^30 caractères
|
de 4 octets à 2Go
|
Caractères Unicode ; serveurs muli-utilisateurs et applications multilingues
|
Pour pouvoir effectuer cette correspondance pour les String qui nous intéressent ici, il va donc nous
falloir créer une instance de l'objet TJNIEnv, puis utiliser la fonction de conversion fournie par
cet objet : JStringToString, ce qui modifie notre code source en :
| code Delphi : 'DDEToJava.dpr' |
library DdeToJava;
uses JNI,DdeMan;
function Java_DdeToJava_getNCE (PEnv: PJNIEnv; Obj: JObject; Arg1 : JString; Arg2 : JString; Arg3 : JString): JInt; stdcall;
var
JVM: TJNIEnv;
begin
JVM := TJNIEnv.Create(PEnv);
Result := 0;
with TDdeClientConv.Create(Nil) do
try
ConnectMode := ddeManual;
SetLink(JVM.JStringToString(Arg1),JVM.JStringToString(Arg2));
if OpenLink then
Result := StrToInt(RequestData(JVM.JStringToString(Arg3)));
finally
Free;
JVM.Free;
end;
end;
exports
Java_DdeToJava_getNCE;
end.
|
Il faut bien entendu ne pas oublier de modifier la fonction native du fichier Java, en rajoutant nos
3 arguments chaines :
| code Java : extrait de 'DDEToJava.java' |
native public static Integer getNCE(String s1, String s2, String s3);
|
VI. Compilation du code source Delphi
Il ne nous reste plus qu'à compiler ce code source pour avoir une dll utilisable. Pour la compilation,
il est nécessaire que le fichier 'DDEToJava.dpr' soit dans le même répertoire que 'jni.pas' et 'JNI_MD.INC'.
On compile à l'aide du menu 'Projet', puis 'compiler DDETojava'.
On a alors notre dll qu'il faut placer dans le répertoire System32 de Windows, avant d'exécuter le
programme DDEToJava.java . Une solution plus intéressante si ce n'est pas une dll partagée, consiste
à la placer dans le répertoire du programme java et de modifier l'appel à la dll comme suit :
| code Java : extrait de 'DDEToJava.java' |
System.load(System.getProperty("user.dir")+File.separator+"DDEToJava.dll");
|
VII. En conclusion
Cet exemple a permit de découvrir la mise en oeuvre de JNI, à travers un besoin bien spécifique, en utilisant
Delphi. La documentation de Matthew Mead sur ce sujet est assez complète, et peut permettre de résoudre
des situations bien plus complexes.
VIII. Remerciements
Je remercie
Ricky81 qui m'a guidé pour la publication de cet article,
Nono40 et
Pedro pour l'optimisation du code Delphi et
Diogène pour ses corrections orthographiques.

