Table of Contents

Commit 2008-08-25

Modifs

CurveReZoner python

Director SWIG

Dans le but de permettre à mon TFiste de coder facilement un ReZoner de lignes; j'aurais aimé que l'on puisse dériver un objet python de CurveReZoner. Ceci pour pouvoir tester les algorithmes en restant sous python (plus facile, plus rapide, …)

Pour dériver une classe C++ en python, il faut passer par SWIG et indiquer que la shadow classe générée par SWIG est dérivable. SWIG met alors en place un système élaboré pour pouvoir les fonctions virtuelles reprogrammée en python à partir du code C++.

Le système est astucieux: lorsqu'on crée un objet de la classe dérivée, SWIG crée un objet appelé “director” qui est dérivé d'une part de la classe de base C++ et d'autre part d'une classe SWIG nommée Director. L'objet “director” implémente toutes les fonctions virtuelles suceptibles d'être redéfinies en python dans une classe dérivée. Chaque appel C++ aboutit donc dans les fonctions membres du “director” qui se charge de décider si il faut appeler du code python (si la fonction a été redéfinie) ou du code C++ (dans le cas contraire).

Les problèmes arrivent quand on essaye de mixer tout ça avec une gestion de mémoire un peu évoluée, par exemple nos compteurs de référence. En effet, lorsqu'on utilise pas de directors, l'objet python (shadow classe) peut être en général détruit même si l'objet C++ sous-jacent est conservé. Par exemple, si on crée une ligne (Line), un objet python Line est créé par python et celui-ci se charge d'allouer un objet équivalent en C++. Lorsque l'objet python doit être détruit (parce que plus aucune ref ne pointe sur lui), il appelle une commande de destruction de l'objet C++ que nous avons remplacée par un simple decRef(). On peut ainsi conserver l'objet qu'on a créé sans garder son équivalent python.

Si maintenant l'objet python à été dérivé d'une shadow classe, il est évidemment indispensable que cet objet python soit intimement lié à l'objet C++ (qui est un director de sa classe de base). On ne peut jamais détruire l'un sans détruire l'autre. Si on applique brutalement le mécanisme décrit ci-dessus, l'objet python va être détruit et le code va planter lorsque le C++ va appeler une fonction virtuelle de l'objet.

Pour contourner le problème, SWIG propose simplement une méthode nommée disown() qui fait en sorte que python n'appelle plus le destructeur. La désallocation doit donc être faite en C++. Un appel à cette méthode peut être ajouté au constructeur de la shadow classe pour que ça soit transparent à l'utilisateur (Luc a fait ça pour ses extracteurs python).

Cependant, le fait que python ne s'occuppe plus du tout de cet objet est ennuyeux au niveau du comptage de références: on a créé l'objet (ref=1), on l'a ajouté à un ensemble avec un add (ref=2) mais le detructeur python n'étant jamais appelé à partir de python, on ne redescendra jamais à 0. la destruction de l'ensemble fera simplement retomber ref à 1, ce qui est insuffisant pour détruire l'objet. On a donc un beau memory leak.

Il y a un moyen simple de modifier le code pour que ça fonctionne tout de même: il suffit de ne pas incrémenter le compteur lorsqu'on crée l'objet (en modifiant la directive %ref de SWIG). L'objet transite donc par un état où ref=0 sans être détruit. Deux problèmes subsistent alors si on choisit cette solution:

Bref, au final, j'ai conservé le memory leak et j'ai écrit ce texte pour éventuellement régler le problème plus tard. Il se pourrait aussi que SWIG évolue et fournisse un mécanisme un peu plus élaboré et moins radical que disown pour gérer la mémoire dans de tels cas.

Interface générique

Visiblement il y a un petit problème avec les fonction protégées de les directors SWIG: bien qu'i soit tout à fait possible de surdéfinir une fonction virtuelle protégée en python, il ne semble pas possible d'appeler une fonction C++ protégée à partir de l'implémentation de la classe dérivée. C'est un peu dommage parce que c'est seulement dû à un garde-fou mis par SWIG. Je crois que c'est dû au fait que SWIG est incapable de savoir si une fonction de ce type est appelée à partir de l'implémentation d'une classe dérivée ou de l'exterieur (dans ce dernier cas, l'attribut “protégé” serait bypassé par SWIG et ce ne serait pas terrible).

J'ai donc dû programmer mon interface en “public” dans CurveReZoner.

Exceptions

Un point important est la gestion des exceptions dans les routines python qu'on va écrire. En effet, vu qu'elles vont être appelées par le C++, l'affichage des erreurs va être perturbé. N'importe quelle erreur va être mentionnée comme “C++ Runtime Error” que ça soit une bête erreur de syntaxe ou qualque chose de plus sérieux. J'ai essayé d'utiliser le système proposé par SWIG (ajouter du code dans la gestion des exceptions SWIG) mais ça ne donne rien de mieux: la pile ne s'affiche toujours pas. J'ai donc décidé d'encadrer toutes mes commandes python par une gestion explicite des erreurs avec affichage de la pile. C'est pas très beau mais ça marche!

Divers

Tous ces développements ont pour but de faciliter l'accès à Metafor à travers python pour mon TFiste.

curveset(1).mesh(n1)

s'écrit maintenant

SimpleMesher1D(curveset(1)).execute(n1)

En powergrep, si vos args ne contiennent pas de parenthèses, voilà ce que ça donne pour le mailleur 1D du 2ieme degré:

\bcurveset\(([^)]+)\)\.secondDegreeMesh\(([^)]+)\)
HighDegreeSimpleMesher1D(curveset(\1),2).execute(\2)

Projet

Fichiers ajoutés/supprimés

mtGeo/mtGeoCircle.inl	added
mtGeo/mtGeoBoundingBox.inl	deleted

Romain BOMAN 2008/08/25 09:10