doSimple Python et SWIG

Dernière modification le 02 May 2006

SWIG pour interfacer une bibliothèque C avec Python

SWIG (Simplified Wrapper and Interface Generator) permet de connecter des langages interprétés comme Python, Perl ou Ruby avec C/C++. SWIG utilise les fichiers header de ces langages pour générer un emballage (wrapper) qui permet d’accéder aux méthodes, structures et objets des langages C/C++.

L’interêt de SWIG est de profiter de l’efficacité des langages compilés C/C++ et de la diversité des différentes bibliothèques construites dans ces langages.

Ce document montre par un exemple comment récupérer des stuctures de données du langage C.

Exemple

Prenons le cas d’une bibliotèque très simple qui manipule des vecteurs.

Télécharger les fichiers d’exemples.

Fichier header "biblio.h"

Le fichier header décris les structures de données et le prototype des fonctions de la bibliothèque

struct Vector
{
        double x,y,z;
        // ce tableau nous sera utile pour 
        // détécter des fuites de mémoire
        double faketab[100000];
};

struct Vector * getUnitVector();
void deleteVector(struct Vector *v);

Fichier de la bibliothèque "biblio.c"

En suivant une syntaxe particulière, il est possible de définir un constructeur et un destructeur pour notre structure. Si on ne le fait pas SWIG les génère automatiquement à partir du fichier header.

#include "biblio.h"
#include <stdlib.h>

// constructeur de la structure pour SWIG
struct Vector * new_Vector()
{
        struct Vector *v;
        v = (struct Vector *)malloc(sizeof(struct Vector));
        v->x = v->y = v->z = 0;
        return v;
}

// destructeur de la structure pour SWIG
void delete_Vector(struct Vector *v)
{
        free(v);
}

// crée un vecteur unitaire
struct Vector * getUnitVector()
{
        struct Vector *v = new_Vector();
        v->x=1;
        return v;
}

// supprime un vecteur donné
void deleteVector(struct Vector *v)
{
        free(v);
}


Fichier d’interface "biblio.i"

Ce fichier d’interface indique à SWIG quel sera le nom du wrapper ainsi que les fichiers sources qu’il doit analyser.

%module biblio
// le code suivant est simplement copié 
// au début de celui du "wrapper" généré
%{
#include "biblio.h"
%}
// le fichier à analyser
%include "biblio.h"

// permet d’enregistrer le constructeur 
// et le destructeur auprès de SWIG
%extend Vector 
{
     Vector();
     ~Vector();
};

Le Makefile

Ce fichier de commande suivant génère les fichiers wrapper en C et en Python puis compile la bibliotèque.

SHELL = /bin/sh
WRAPPER = biblio

make:
   swig -python ${WRAPPER}.i 
   gcc -c ${WRAPPER}.c ${WRAPPER}_wrap.c \
      -I /usr/include/python2.4/
   rm ${WRAPPER}_wrap.c
   ld -shared ${WRAPPER}.o \
      ${WRAPPER}_wrap.o -o _${WRAPPER}.so
   rm ${WRAPPER}.o ${WRAPPER}_wrap.o

Utilisation du wrapper avec Python

Ce script Python permet de montrer comment utiliser cette bibliotèque C afin d'éviter des fuites de mémoire.

Ce code crée 1000 vecteurs, une fois en utilisant la fonction getUnitVector, une fois en utilisant le constructeur.

Si on utilise le constructeur de l’objet Python fourni par le wrapper le destructeur de l’objet Python appelera le destructeur de la bibliothèque Vector automatiquement. Par contre si on utilise la fonction getUnitVector il faut supprimer explicitement la structure afin d'éviter une fuite de mémoire.

#!/usr/bin/python
# -*- coding: utf8 -*-

import sys
import biblio
import time

def main(args):
        i=0
        print 'boucle de test pour le ramasse-miettes'
        print 'controlez l\'etat de la memoire'
        while(i<1000):
                # sans utiliser le constructeur
                if i%2==0:v = biblio.getUnitVector()
                # en utilisant le constructeur
                else:v = biblio.Vector()
                time.sleep(0.01)
                # on supprime explicitement le vecteur
                # si il a n’a pas été créé avec le 
                # constructeur, la propriété "thisown" 
                # donne cette information
                if v.thisown==0:biblio.deleteVector(v)
                i=i+1
        print 'boucle terminee'

if __name__=="__main__":
        main(sys.argv)

Remarques

En utilisant ce script on constate que l'état de la mémoire est stable. Si la ligne biblio.deleteVector de suppression explicite est supprimée, on constate une augmentation rapide de l’utilisation mémoire qui ne sera pas rendue. Il y a donc une fuite mémoire.

Quand Python n’a plus de référence sur un objet le Garbage Collector supprime l’objet Python. Le destructeur de cet objet Python commande à la bibliothèque C/C++ la libération de la mémoire via le destructeur programmé en C.

On peut donc profiter pleinement du Garbage Collector de Python tant qu’on respecte la règle de ne pas commander la création d’une structure dans une fonction C/C++.

Liens connexes

Auteur
Batiste Bieler
Licence
Les exemples de code sont sous LGPL
Vous pouvez distribuer librement l’ensemble de ces documents en respectant le droit d’auteur.