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:
- si l'objet n'est jamais passé au C++, il ne sera jamais détruit.
- si on veut dériver une classe C++ de la classe de base sans director, elle ne sera pas compatible avec ce système…
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.
- Interfaçage SWIG de Vect3Vector, PointVector
- Interfaçage de Configuration sous SWIG
- Les configurations de base sont maintenant “const”.
- Ajout du mot clef “virtual” aux déclarations de toutes les fonctions virtuelles de la géométrie.
- Nettoyage de quelques inl dans lesquels étaient définies des fonctions virtuelles.
- Interfaçage SWIG des mailleurs.
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