Emporte Une Vache !!!

jeudi, octobre 20, 2005

Python : first sight

Cela fait presque une semaine maintenant que je travaille sur un binding OpenGL avec les derniers drivers nVidia.

Jusque-là je travaillais sur d’anciens headers datant de 2003, qui me donnaient entière satisfaction, car j’ignorais l’avancée récente des technologies graphiques. Avec la découverte de gl_framebuffer_object et gl_pixel_buffer_object, et autres sucreries, il m’a fallu me remettre en cause.

Un binding OpenGL ?

En C pour travailler avec OpenGL il « suffit » de faire #include et à la rigueur la même chose pour le header de glut quand on travaille avec Glut.

Les choses ses corsent quand on veut faire la même chose en Ada. On ne peut pas faire #include . On ne peut pas appeler une fonction C sans au préalable l’avoir déclarée, avec les types corrects et avoir déclaré qu’il s’agissait d’une fonction importée d’un autre langage…

En gros, si on a une fonction dans une DLL (j’ignore si ça marche aussi avec une bibliothèque statique) il faut procéder ainsi :
  • Supposons qu’en C on ait la fonction :
unsigned char cEstCoolTaVie(int genre, float crari);
  • En Ada on déclarera (si on le fait à la main) :
function C_Est_Cool_Ta_Vie(Genre : Integer ; Float : Crari)
return Interfaces.C.unsigned_char;
  • Il faudra ensuite écrire dans le source Ada
Pragma Import (C, C_Est_Cool_Ta_Vie, "cEstCoolTaVie");


A propos de la taille du code (le double de lignes ici) : Ada est un langage plus verbeux, c’est sûr. Mais en général, comme on a moins de vérifications à ajouter dans le code en permanence – beaucoup sont faites par défaut par le langage – le code est souvent plus court et concis.

A propos de la notation, qui est critiquable (majuscule ET underscore ?) : c’est une convention de style du langage ou du compilateur. On est pas obligé de la suivre (la casse n’a aucune importance en Ada), mais la plupart des développeurs Ada y sont habitués, et les IDE Ada font le passage en majuscule automatiquement. On peut aussi utiliser gnatpp (pp pour pretty print), outil en ligne de commande assez configurable.

Les problèmes

Bien, maintenant si vous voulez regarder un instant un header glext.h sur lequel je travaille en ce moment.

Il fait plus de 5000 lignes et c’est un header C, avec les sympathiques instructions au préprocesseur pour définir des constantes et garder du code ou pas… Vraiment du code comme je l’aime : efficace - cà ! en performance c’est sûr… -, et compréhensible.


Ce qu’on peut voir c’est que « globalement », on a 3 parties intéressantes dans chaque header gl.h et glext.h :
  • des redéfinitions de types C (typedef), préfixés en GL, collé. Par exemple : GLint, GLuint, GLubyte, GLfloat…
  • des définitions de constantes « en dur » à l’aide de #define. Cela veut dire qu’en C ou en C++, à la précompilation le nom des constantes qu’on aurait pu insérer dans notre code sera remplacé « textuellement » par la valeur définie. Les constantes ont pour charte d’être entièrement en majuscules et les mots sont séparés par des caractères underscore ‘_’. Par exemple : GL_TRUE, GL_FALSE, GL_STREAM_DRAW.
  • des fonctions. Préfixées en gl (minuscule) et ensuite avec un style « dromadaire ». Par exemple : glDrawBuffers, glCreateShaderProgram.

Si on suppose qu’on veut séparer les fonctions OpenGL de base des extensions, on se retrouve avec 4 catégories d’éléments.

Mon job cette semaine, après avoir analysé et tergiversé sur les fonctionnalités récentes que j’allais utiliser pour multiplier la vitesse de l’appli OpenGL sur laquelle je travaille, c’est de transformer tout ce fatras de headers C de façon à pouvoir utiliser OpenGL en Ada, y compris les dernières extensions. Et, bien sûr il faut que l’expérience soit reproductible : les headers changent en fonction des constructeurs et des versions et des extensions…

Il faut donc que le processus de génération du binding soit automatisé au maximum.


Première étape : le gros outil pas terrible

Pour écrire un binding vers du C en Ada, en général on se pédale les signatures de fonction à la main, ainsi que les pragma import.

Mais ici, vu la taille du header, même pas la peine d’y penser. Alors j’ai cherché s’il existait des solutions, si possible « éprouvées ». J’étais très enthousiaste, car mon tuteur m’avait dit que le header qu’il m’avait passé, et dont je me servais depuis un mois, et qui fonctionnait très bien à quelques retouches près, avait été généré avec un « logiciel » appelé c2ada.

Mon enthousiasme s’est vite dégonflé. Le makefile de base, pour compiler c2ada, est assez mal fait et il faut un peu mettre la main au cambouis. Ensuite une fois qu’on a son exécutable – non sans des dizaines de warning à la compilation et même à l’édition de liens – il faut encore bidouiller $PYTHONPATH pour arriver à le lancer pour la première fois. J’ai essayé chez moi sous Windows, c’est encore pire… Bon, un après-midi perdu comme ça…

Le résultat fourni par c2ada est plutôt décevant. D’abord il lui faut inclure toutes les dépendances et donc en générer des sources Ada. Ainsi, glext.h faisant référence à des variables et types définis dans gl.h, il faut faire un petit script pour ajouter #include "gl.h" au début du fichier.

Les fonctions dans le header C sont déclarées (avec des APIENTRYP WINGDI et GLAPI…), puis plus tard dans le fichier on trouve leur signature. Bien sûr, c2ada ne retrouve pas ça tout seul. A priori, on dirait presque qu’il traduit ligne par ligne. Il faut donc encore un script pour retrouver les correspondances et virer les APIENTRYP et autres joyeusetés dédiées au préprocesseur, dont on a rien à faire en Ada.

Résultat, après avoir bataillé sec, on se retrouve avec plusieurs fichiers Ada, même pas compilables et bien sales.

Il faut donc se contenter du résultat. Bon en réalité je résume, parce que j'ai essayé au maximum d'utiliser les fonctionnalités proposées par c2ada, pour limiter la casse.

Mais encore une fois, il va être nécessaire de passer un script, mais sur du code Ada cette fois-ci.


Deuxième étape : l’outil puissant

En partant de ce code Ada tout pourri généré par c2ada, il me faut arriver à 4 « beaux » paquetages, contenant chacun les éléments qu’il faut.

Il faut aussi que soit respectée la Convention_De_Nommage_Ada, ce qui met en conflit les types, les fonctions, et les constantes, très proches "graphiquement". D’où l’intérêt des paquetages séparés.

Bien sûr les types ne sont pas convertis comme il faut par défaut. Il faut virer la dépendance à stddef.h et changer les ptrdiff_t pour ce que ça peut représenter… Les types contenus dans gl.h et glext.h peuvent se recouvrir, il faut les fusionner pour ne pas avoir de déclaration en double.


J’en passe et des meilleures...

Alors je me lance dans l’écriture d’un script Python pour faire tout ça. Oui, Python. En général je n’aime pas trop les langages de script. Pas typés statiquement, trop "je code à l’arrache et on verra" (mais on ne voit jamais en fait) et lents.

Mais la sagesse populaire dit : chaque langage a son utilité. Ou plutôt chaque type de langage a son champ d’utilité.

Or, pour ce que j’ai à faire j’avais besoin :
  • d’un bon système pour détecter les expressions régulières
  • de pouvoir faire relire le code =o)
  • de pouvoir coder assez vite sans avoir besoin de la vérification apportée par le typage
  • de fonctions de traitement de chaînes de caractères et d’entrées-sorties, simples et directes
Alors je suis parti d’un script qu’avait déjà commencé à coder mon collègue pour « Adaifier » les fonctions… J’avais sous la main le bouquin « Introduction à Python » et l’aide en ligne Python. Et j’avais cet exemple de code qui faisait la majorité de ce que j’aurais à faire :
  • compilation d’une regex
  • parcours du fichier source
  • match de la regex
  • reformatage ou autre
Passée la première phase de bidouille, j'ai très vite et très facilement compris comment ça foncitonne. Du coup en une journée j'adapté mes regex à mon code, fait des expressions « récursives » - terme bien pompeux par rapport à la réalité : il s’agissait juste de matcher des expressions dans des expressions dans des expressions, en appelant successivement des regex différentes… - testé, recommencé, ajouté encore des regex, encore des conditions, encore des fonctionnalités. On est loin des grammaires...

Ca allait très vite et j’ai pris plaisir à coder aussi simplement. Tout ce dont j’avais besoin était sous la main : liste, map, expressions régulières, traitement de chaînes flemmard-friendly, fichiers, et je pouvais très vite tester, regarder, modifier, tester, regarder, modifier…

Encore avant hier, j’étais un peu effrayé par la simplicité apparente du langage, alors j’avais vaguement lu quelques notions de syntaxe Python et c’est tout. Voyant les codes bizarres que mes camarades pythonistes postaient de temps en temps sur IRC (la vraie vie), je me disais qu’en fait il s’agissait plus d'un perl bis/beurkque d'un « bon langage simple ». Je m’étais trompé. Une fois de plus ça m’apprendra à penser/supposer sans tester.

J’avais déjà fait du « script » assez longtemps et pensais avoir fait le tour. Franchement vbs (Microsoft ASP) et vba (Microsoft Office) et javascript (dans les DOM HTML et SVG) étaient vite devenus pour moi des boulets plutôt que des outils appréciables qui auraient du faire gagner du temps, avec lesquels je n’ai que rarement apprécié coder. Et encore Javascript restait *relativement* intéressant parce qu’on pouvait manipuler le DOM SVG assez facilement.

Alors qu’en Python, j’ai l’impression que tout est simple, déjà là. Au point même que j’avais du mal à m’enlever de la tête la question suivante : « à part pour les applications de performances très très pointues ou encore pour des applications sûres, à quoi peuvent servir les autres langages après Python ? ». Peut-être qu’on fait trop de développement « simples » en C ou C++ juste « parce que ».

Le même argument vaut pour Ada ou Eiffel : j’ai l’impression que peu de décideurs et de développeurs se posent la question « le bon outil pour le bon job ». Je connais peu C++ mais est-on obligé de l’utiliser PARTOUT ? Si je veux écrire un logiciel de façon ultra-structurée, sûre (éviter trop d’overflow) ; passer mon temps à coder (après la phase de conception bien sûr) plutôt qu’à débugger ; avoir immédiatement le multi-tâches, des moniteurs en 3 lignes de code, des objets protégés… Gérer la mémoire finement via Storage Pool et ne pas trop m’inquiéter pour les fuites mémoire… Si je veux tout ça, je me tournerai vers Ada.

Si je veux de l’ouverture, pouvoir utiliser toutes les bibliothèques C++ du marché, organiser mon code un peu comme je veux... J’utiliserai C++.
Peut-être qu’en avançant plus dans le Stroustrup, d’autres arguments de choix me viendront…

En tout cas, l'argument "je n'arrive pas à recruter des programmeurs Ada qualifiés" est franchement fallacieux. L'apprentissage d'Ada est très rapide. En 2 semaines on est formé à l'essentiel. Et ensuite, des revues de code, quelques petits projets simples proches du sujet du travail. En général c'est plus que suffisant. Ada et Eiffel sont, par leurs contraintes et la qualité de leurs compilateurs, des langages qui s'apprennent presque "tout seul". En tout cas, très rapidement.

A quand une front/backend Swig pour Ada, ou encore un binding maison pour intégrer Python dans une application Ada ? Je vais regarder du côté de GPS.

Conclusion : une très bonne expérience

Ainsi, à la fin de cette journée, il me reste une impression de grand plaisir à coder, comme après une bonne journée de codage Ada.

Le script n’est pas fini, il fait déjà 400 lignes et fait déjà énormément de travail. Je l’ai étendu avec plein de regex (expressions régulières), et des conditions des plus en plus compliquées, et ça fonctionne encore sans que mon cerveau n’explose ou que je ressente le besoin de « partitionner » ou ranger le code (je commente bien sûr).

Seule remarque : malgré tout je trouve que l’indentation pour les blocs, même si c’est une idée intéressante, c’est PITA =o).

En deux jours j’ai réussi à améliorer/nettoyer mon binding généré de 90%. J’en suis même au point que le code généré compile sans même un warning.

Comme on dit par chez moi :
YaY !
et non pas LOLJesus ou encore LMAOZedong...