Artisan Numérique

/vintage/zaurus/ Développer en Java sous pdaXrom

Dans la mesure où Java est mon langage fétiche, il fallait que je vois ce que cela donne avec le Zaurus... L'objectif (il en faut toujours un) est déjà bien sur de voir si Java est compatible avec cette petite plate-forme et, dans un second temps, tenter de faire un petit lecteur de média en combinant une interface graphique et l'excellent mplayer.

Trois mots sur Java

Il en va de java pour le monde embarqué comme des autres langages. Il est possible soit de compiler directement sur la plateforme (le Zaurus), soit d'effectuer ce travail sur une machine plus puissante (une machine de bureau) et de n'exécuter que le binaire sur le module embarqué. Pour info, il existe un compilateur de bytecode java que l'on trouve dans les contributions (jikes) mais j'avoue ne pas bien voir l'intérêt et de loin préférer tout compiler sur une machine plus puissante. Cette stratégie est d'autant plus crédible que l'aspect multi-plateforme de Java permet d'oublier facilement à quelle machine le binaire est destiné.

Pour comprendre comment fonctionne Java il faut distinguer trois aspects : Le langage Java , La machine Virtuelle Java (ou JVM) et les librairies de fonctions Java (ou JDK).

Le langage Java est un langage Objet. Tout ou presque en Java est objet. Sa syntaxe est héritée du C (et pas de C++) et a été conçue pour un maximum de lisibilité. Tout ce que l'on imagine d'un langage moderne est présent dans Java : héritages, interfaçage, exceptions,prototypes, etc... Les seules choses qui ont été exclues sont celle qui, de l'avis des concepteur du langage, compromettent la stabilité de la structure (ex. héritages multiples).

La machine virtuelle Java exécute le bytecode issue de la compilation du langage. Pour comprendre à quel point cette machine est indépendante du langage il suffit de regarder du côté de dotNet. Si C# est le langage de prédilection sur cette plateforme, l'éditeur propose aussi un compilateur Java qui fournit un bytecode exécutable sur une machine virtuelle dotNet. Il existe aussi des traducteurs qui permettent de lancer le bytecode dotNet sur une machine virtuelle Java. On peut même imaginer écrire en assembleur JVM sans passer par Java. Ces deux choses sont donc bel et bien indépendantes.

Les librairies java, appelé le JDK. Ce sont l'ensemble (très volumineux) de classes utilitaires fournis au développeur pour accéllerer son oeuvre. Il y a de tout là dedans, de l'interface graphique (nous y reviendrons) aux structures de stockages (tables de hashage, vecteurs, etc.) en passant par la gestion de la vidéo, de la 3D, du réseau, etc...

Sun étant en quelque sorte le mainteneur de Java, les 3 composants que je viens de citer sont généralement marqués d'un numéro de version commun. Par exemple Java 1.5 fait référence à l'évolution 1.5 du langage (apportant les prototypes et les annotations), l'évolution 1.5 de la machine virtuelle et de son langage bytecode (beaucoup plus rapide) et l'évolution du JDK 1.5 avec de nouvelles fonctions et librairies.

Mais comme, et c'est ce qui va nous intéresser pour le Zaurus, ces composants sont indépendant, on peut fort bien imaginer utiliser les librairies d'un éditeur tiers, la JVM d'un autre en compilant notre code Java avec le compilateur de Sun. Tant que chacun respecte les numéros de version et les spécifications associées, tout doit bien se passer.

Donc si nous allons, sur notre PC, compiler nous sources avec les paquets de Sun, sur le Zaurus, il seront exécuté par la machine virtuelle libre JamVM et les librairies libres GNU/Classpath. Et tout ce petit monde, nous allons le voire, va marcher en cadence.

Discussion sur les performances

Java joui d'une mauvaise réputation en terme de performances, ce qui est sommes toute ridicule. Java se classe dans la catégorie des langages semi-interprétés (bytecode exécuté dans une JVM). Ce n'est donc pas lent comme du Basic qui est interprété à partir du source lui-même mais évidement plus lent (en encore ;-) que du C compilé directement assimilable par le micro processeur.

Dans la catégorie des langages semi-interprétés nous avons d'autres concurrents comme Python (en mode pyc) et dotNet. Et étant de la même catégorie, n'en déplaise aux esprits chagrins, les performances sont en moyenne les mêmes. Par exemple une application lancée avec un jdk 1.5 est exactement la même qu'une dotnet 2.0 équivalente (c'en est troublant d'ailleurs). En revanche, une fois lançée, une application Java est, d'experience, beaucoup plus rapide qu'une application Python mais consomme un peu plus de mémoire. Enfin une application Java sera en moyenne toujours, 2.5 fois plus lente qu'une appli en C compilée avec GCC (optimisation O2) mais aura exactement les mêmes performances qu'une application en C sans optimization.

En terme d'occupation mémoire aussi les choses sont comparables. J'ai comparé avec pmem une application python et son équivalent Java et l'occupation se bat à 1Mo près (en faveur de Python).

Bref, utiliser un semi-interprété présente l'avantage d'un langage souvent plus évolué (dit langages managés), de plus de facilités de développement, mais aussi a un coût en performances quel que soit le langage choisi. Une fois ceci dit, j'avoue largement préférer un langage Java que je trouve plus mature qu'un dotnet (je n'ai pas testé dotNet après la 1.1) ou Python. Mais ce n'est qu'une histoire de goûts.

Préparation du PC

La première chose à installer est donc le J2EE (pour Java 2 Entreprise Edition) 1.5 release 6 à partir du site JavaSoft (Sun) ainsi que l'excellent EDI, Eclipse 3.2.

Nous pourrions évidement écrire nos applications Java avec un simple NotePad mais ce n'est pas bien pratique et la plate-forme de développement Eclipse semble plus indiqué pour ce type d'opération. Pour ceux qui ne connaissent pas, Eclipse est un projet libre initié (et offert) par IBM, destiné à construire un environnement de développement ultra-complet. Et même si ce projet était originellement destiné à Java, il est aujourd'hui capable de prendre en charge à peu prés tous les langages : C, Python, php, dotnet, etc... Eclipse peut aussi être mis en oeuvre facilement sur de nombreuses plate-formes (y compris macOS).

Une fois le J2EE et Eclipse installés, il suffit de lancer Eclipse et de créer le nouveau projet ZaurusJava.

  1. File/New/Project/JavaProject, Appelezle Zaurus puis Finish Avant d'aller plus loin, il convient de modifier les propriétés du projet pour qu'il utilise le JDK 1.4 et non pas le 1.5 qui nous sert à lancer Eclise :
  2. Sélectionner le projet dans l'explorateur et clique-droit Properties
  3. Aller en section Java Build Path, onglet Source
  4. Cliquer sur Add Folder et tapez Sources puis valider
  5. Changer la valeur de Default Output Folder pour zaurus/classes
  6. Aller dans l'onglet Libraries, SSélectionnezle JDK 1.5 puis Remove
  7. Cliquez sur Add Library, puis Next, puis Alternate JRE, Installed JRE, Add, et Browse
  8. Sélectionner le chemin où se trouve le jdk 1.4 (sous linux en /usr/java/jdk1.4.2)
  9. Changer la valeur jre Name en jdk14 puis OK et encore OK
  10. De retour sur Alternate JRE, sélectionnez le nouveau jdk14 puis Finish
  11. Aller dans la section Java Compiler
  12. Cliquer sur Enable Project Specific Settings
  13. Modifier Compiler Compliance Level à la valeur 1.4
  14. Valider par OK Voilà qui en fait, il suffit maintenant faire un click-droit dans l'explorateur Java sur la branche sources et sélectionner new/Class. Taper dans la zone name le nom HelloWolkd et cliquer sur Finish.

A ce stade, vous avez un squelette de la classe HelloWorld dans l'éditeur. On va le modifier pour afficher la bienvenue chère à nos vieux amis Kernighan et Ritchie. Pour corser la tache de la JVM et vérifier par le fait la compatibilité avec les dernières extension du langage Java, nous allons faire le HelloWord le plus idiot du monde, sous forme de prototype :

  public class HelloWorld
  {
public static class SayWhatEver<E>
{
  private E content;
  public SayWhatEver(E value)
  {
    content=value;
  }
  public void sayIt()
  {
    System.out.println(content);
  }
}
public static void main(String[] args) 
{
  SayWhatEver<String> t = new SayWhatEver<String>("Hello World!!");
  t.sayIt();
}
  }

Pour vérifier que tout fonctionne, dans l'explorateur Java, cliquer droit sur le fichier HelloWorld.java et sélectionnez run as puis Java Application. Le message de bienvenue doit s'afficher dans la console en dessous de l'éditeur. Fin de la première étape.

Paramétrage du Zaurus

Côté Zaurus, un contributeur, Cortez, que je félicite au passage, a compilé pour pdaXrom la librairie GNU/Java Classpath et la machine virtuelle JamVM. Classpath est un projet GNU qui c'est fixé comme objectif de fournir une implémentation libre de l'ensemble des classes de base du JDK de Sun. Cela peut paraître idiot de tout refaire ainsi mais c'est loin d'être le cas pour des raisons de licences que je n'approfondirais pas ici.

JamVM est quant à elle une extraordinaire petite machine virtuelle Java dont la taille d'exécutable frise le ridicule (110ko sur une plateforme Intel).

Ceci fait vous pouvez vérifier que la vm est installé en tapant

jamvm -h

Tout vas bien donc, sinon, il faut me prévenir car j'aurais surement fait une erreur dans la création du paquet ;-)

Il faut maintenant remonter le binaire HelloWorld compilé par Eclipse sur le Zaurus pour tester tout cela. Comme nous l'avons paramétré, tous les binaires compilés par Eclipse sont sur votre PC dans le dossier zaurus/classes. Il suffit donc de recopier ce dossier sur une SD et de la mettre dans le Zaurus. Une autre méthode plus rapide consiste à utiliser ssh en tapant :

scp -r /home/mon_home/workspace/zaurus/classes root@zaurus:/mnt/card

Cela me permet de recopier rapidement tous les fichiers compilés sur la SD insérée dans le le Zaurus.

D'une manière ou d'une autre, votre dossier classes est sur la SD en /mnt/card/classes. Normalement il y a un deux fichiers dans ce dossier, le java compile HelloWorld.class et celui de sa classe interne. Pour l'exécuter tapons "simplement" :

/usr/java/jamvm/bin/jamvm -cp /mnt/card/classes HelloWorld

Hello World !!! C'est gagné :) Un premier pas de fait et la vérification au passage de la compatibilité avec la norme 1.5 du langage, en tout cas pour les prototypes ce qui est déjà plus que pas mal !

Maintenant ajoutons la couche graphique à cet édifice bien prometteur...

Premier essai en SWING

Il y a 3 toolkit graphique sous Java. AWT, l'ancêtre dont le crédo est "le plus petit dénominateur commun". AWT ne connaît donc que les composants qui existent sur toutes les platef-ormes (X11, Windows et Mac OS). N'y cherchez par exemple pas de composant "arborescence", il n'y en a pas. En revanche c'est très rapide car ce sont directement les composants natif qui sont affichés.

Fort de cette pauvreté est apparu JFC (Java Foundation Classes) qui jette les bases de composants graphiques entièrement écrits en Java. Cela va s'appeler plus tard SWING et être intégré aux J2EE 1.3. C'est très beau, très très riche et complet mais aussi très lent. Car chaque pixel de chaque bouton est écrit en Java, en bytecode donc, en interprété quoi... Au fil des versions Swing a prodigieusement gagné en vitesse. Aujourd'hui, sans sa version 1.6, Swing est très abouti et parfaitement utilisable pour une machine un tant soit peu puissante. Mais il reste à mon sens un peu trop lourd pour notre pauvre Zaurus.

Dernière arrivé, SWT se veut la synthèse, le meilleur des deux mondes. Son crédo c'est utiliser le natif à chaque fois que possible, sinon, au pire cas, émuler en java. L'IHM d'Eclipse est écrite en SWT. Les SWT reposent sur des wrappers qui encapsulent des toolkits natif comme Win32, GTK ou Quartz. C'est très léger et c'est surtout très rapide.

Commençons donc par SWING, pour déjà voir si notre VM sais ce que c'est qu'une fenêtre. Nous allons modifier notre HelloWorld sous Eclipse :

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

public class HelloWorld
{
  public static void main(String[] args)
  {
    JFrame frame = new JFrame("Hello World");
    JButton button = new JButton();
    button.setText("Quitter");
    button.addActionListener(new ActionListener()
    {
      public void actionPerformed(ActionEvent e)
      {
        System.out.println("bye bye");
        System.exit(0);
      }
    });
    frame.getContentPane().add(button);
    frame.setSize(new Dimension(100,30));
    frame.setVisible(true);
  }
}

Même procédure que pour le précédent exemple, on compile et exécute sous Eclipse pour vérifier que tout fonctionne, et on transporte classes sur le Zaurus pour vérifier que tout se passe sans problèmes. C'est déjà un très bon point...

Passage à SWT

Mon HelloWorld en Swing met près de 2 secondes de plus à se lancer que son équivalent Python (avec pyGtk). Et une fois lancer, la version Swing répond beaucoup moins bien (latence). La raison en est simple, Swing dessine absolument tous les composants en Java alors que PyGtk se "contente" de transférer la gestion des contrôles à des libraires GTK natives. On ne boxe pas dans la même catégorie et pour le Zaurus, c'est un problème auquel il faut trouver une solution. SWT semblerait donc bien plus indiqué.

Une fois installé sur le Zaurus, les fichiers .so natifs sont en /usr/lib/swt et le fichier swt.jar en /usr/java/swt. Modifions maintenant sous éclipse notre HelloWorld pour le mettre à la sauce SWT :

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;

public class HelloWorld
{
  public static void main(String[] args) throws Exception
  {
    Display display = new Display();
    Shell shell = new Shell(display);

    Button button1 = new Button(shell,SWT.PUSH);
    button1.setText("Hello World from Zaurus/SWT !!");
    button1.setLocation(0,0);
    button1.setSize(230,30);
    button1.addSelectionListener(new SelectionAdapter()
    {
      public void widgetSelected(SelectionEvent e)
      {
        System.out.println("Hello world here too !!");
      }
    });

    shell.pack();
    shell.open();
    while(!shell.isDisposed())
    if(!display.readAndDispatch())
    display.sleep();
    display.dispose();
    System.out.println("Goodbye World...");
  }
}

Nous pouvons tester cette nouvelle mouture sous Eclipse en modifiant un peu notre projet pour qu'il prenne en charge SWT. Pour cela il faut aller dans les propriétés du projets (voir plus haut) et dans la section Java Build Path, onglet Libraries pour cliquer sur Add External Jars... et rechercher dans le dossier d'installtion d'éclipse et son sous dossier plugin un fichier .jar de la forme org.eclipse.swt.XXX avec XXX correspondant à votre plateforme (linux.x86_3.1.1 pour un linux, j'imagine un win32 quelque chose pour Windows, etc...). Ceci fait, Valider les propriétés du projet et lancez votre application en cliquant-droit sur HelloWorld.java et en sélectionnant run as/SWT Application.

Si tout c'est bien passé, il faut, comme toujours, transférer le dossier classes sur la SD et lancer sur le Zaurus en précisant cette fois les accès aux librairies SWT :

jamvm -cp /usr/java/swt/swt.jar:/mnt/card/classes -Djava.library.path=/usr/lib/swt HelloWorld

A ce point vous devez avoir un HelloWorld en SWT qui fonctionne.

La référence /usr/lib/swt peut-être éliminée en plaçant dans le fichier /etc/profile, à la fin :

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/usr/lib/swt

Ainsi, à votre prochaine connection (ou reboot, ou en tapant . /etc/profile) la syntaxe sera grandement simplifiée :

jamvm -cp /usr/java/swt/swt.jar:/mnt/card/classes HelloWorld

Conclusion

La première chose que l'on note à ce stade c'est la vitesse d'exécution. Alors qu'en Swing mon HelloWorld s'affiche en 7 seconde, contre 5 secondes pour Python, cela ne met plus que 4 secondes dans cette nouvelle mouture.

Pour compléter ce test j'ai essayé le toolkit fltk, censé être le plus rapide. Il est natif et l'application s'écrit en C++. l'ensemble se cross-compile très facilement et fonctionne très bien sur le Zaurus, éditeur d'écran compris (Nommé FLUID), et en version 1.x ou 2. Et un constat s'impose, le temps de chargement bât tout les records. 2 secondes pas plus pour notre HelloWorld. C'est très encourageant à un détail près, le look de fltk est juste atroce et jure avec le reste du bureau. En fltk2 c'est un peu mieux et les développeurs nous promettent des styles GTK. Mais s'agira t'il d'un wrapper ou d'un simple thème ? Bref,pour l'instant c'est sur, c'est rapide mais cela ne balance pas suffisamment la rudesse d'un développement en C++ et la laideur du résultat.

Pour l'instant, je reste donc sur la plate-forme Java. Nous bénéficions ici d'un environnement mature et ultra riche. L'exécution en elle-même est très compétitive et ce n'est que le temps de chargement qui diffère réellement avec le natif. En revanche, comparé à Python, il n'y a juste pas photo...