Copyright © 2000 Pramode C.E
Copyright © 2000 Gopakumar C.E
Copyright © 2000 Pierre Tane
Article paru dans le n°49 de la Gazette Linux de janvier 2000.
Cet article est publié selon les termes de la Open Publication License. La Linux Gazette n'est ni produite, ni sponsorisée, ni avalisée par notre hébergeur principal, SSC, Inc.
Table des matières
Les langages de scripts tels que Perl, Python et Tcl sont souvent sous les feux de la rampe ces temps-ci en vertu des facilités qu'ils offrent en termes de Développement et de Prototypage Rapide d'Applications. Il n'est plus à démontrer que l'utilisation de langages tels que Python réduit drastiquement les temps de développement sans oublier que le code résultant est adaptable et très robuste. Mais il se trouve des situations dans lesquelles une approche script pure n'est pas satisfaisante, typiquement par exemple dans les applications scientifiques qui demandent des calculs ou des routines graphiques rapides, ou bien dans les applications qui doivent contrôler et coordonner des périphériques matériels en environnement temps réel. Ce qu'il nous faut dans ce cas, c'est un paradigme de langages mêlés dans lequel les langages systèmes traditionnels tels que C/C++ prennent en charge tout le sale boulot de bas niveau alors que le langage de script se comporte comme le chef d'orchestre. Cet article traite en particulier d'un excellent programme du nom de Simplified Wrapper and Interface Generator (SWIG) (ou Générateur d'Interface et d'Encapsulateur Simplifié dans le but d'intégrer du code écrit en C/C++ dans un script écrit dans le célèbre langage Python. Les bouts de code présents dans cet article ont été testés sur une machine Red Hat Linux (5.2) avec Python ver 1.5.1.
SWIG est actuellement développé par Dave Beazley et peut être téléchargé depuis www.swig.org. L'installation en est simple : il vous suffit de lancer ./configure suivi de make. À cette heure, SWIG supporte :
Perl
Python
Tcl
FSF Guile
Disons que l'on dispose d'une fonction C de prototype add(a,b)
qui retourne la somme des deux
nombres qu'on lui passe en argument. Nous allons voir comment on peut faire en sorte de pouvoir
l'appeler depuis Python. Nous allons commencer par créer un fichier de nom arith.c
qui contient
le code suivant.
int add(int a, int b) { return a+b; }
Exécutons maintenant la commande suivante :
swig -python -module arith arith.c
Nous constatons alors que SWIG a créé deux nouveaux
fichiers : arith_wrap.c
et
arith_wrap.doc
. Il nous faut alors compiler les deux
fichiers C, arith.c
et arith_wrap.c
,
dans le but de produire les fichiers objet. Exécutez donc la
commande :
gcc -I/usr/include/python1.5 -c arith.c arith_wrap.c
Les fichiers objet arith.o
et arith_wrap.o
doivent maintenant être combinés afin de produire un
objet partagé de nom arith.so
:
ld -shared -o arith.so arith.o arith_wrap.o
Si tout se passe bien, nous avons maintenant un fichier appelé arith.so
dans le répertoire courant. Voici
un exemple d'utilisation du module arith :
import arith >>>arith.add(10, 20) 30 >>>arith.add(10, -10) 0 >>>
C'est ce que nous allons voir ! Ajoutons donc une fonction de plus dans notre fichier arith.c
et
reconstruisons arith.so
:
int fact (int n) { int f=1; while (n > 1){ f = f * n; n = n - 1; } return f; }
Codons donc une fonction similaire en Python (sauvez la dans un fichier fact.py
) :
def fact(n): f = 1 while n > 1: f = f * n n = n - 1 return f
Écrivons donc maintenant un programme de profilage sommaire, profile.py
:
#!/usr/bin/python import fact, arith, time pyfact = fact.fact cfact = arith.fact # Mesure de la vitesse de cfact start = time.time() for i in range(1,100000): cfact(10) end = time.time() print 'Factorielle en C a pris', end-start, 'secondes' start = time.time() for i in range(1,100000): pyfact(10) end = time.time() print 'Factorielle en Python a pris', end-start, 'secondes'
Sur notre vieux Pentium, cela a donné :
Factorielle en C a pris 1.29531896114 secondes Factorielle en Python a pris 8.22897398472 secondes
SWIG génère des encapsulateurs non en s'intéressant à la manière dont votre code C fonctionne en interne mais plutôt en observant la spécification de l'interface. Voici comment nous aurions dû procéder.
Tout d'abord, créons un fichier d'interface, arith.i
. Ce fichier devrait contenir les lignes
suivantes :
%module arith extern int add(int a, int b); extern int fact(int n);
Générez alors les encapsulateurs en lançant la commande swig -python arith.i, compilez
les fichiers objet et utilisez ld pour créer arith.so
.
Le port parallèle du PC peut être utilisé pour effectuer des expériences d'interfaçage de matériel
très amusantes. Sous Linux, on dispose des fonctions inb()
, outb()
,... qui peuvent être
utilisées pour accéder aux ports d'E/S. Voici un programme C qui écrit sur le port imprimante.
#include <asm/io.h> int main() { iopl(3); outb(1, 0x378); }
Le programme doit être compilé par cc -O2 io.c et exécuté avec des droits de super-utilisateur.
L'appel à iopl
est requis pour autoriser l'accès aux ports E/S aux programmes utilisateurs. Comment
écrire une version Python de ce programme ? C'est simple. Utilisez SWIG pour avoir des versions
appelables en Python de outb()
, inb()
et iopl()
. Suit un module io.c
spécialement
conçu pour SWIG :
#include <asm/io.h> int py_iopl(int level) { return iopl(level); } void py_outb(unsigned char val, int port) { outb(val, port); } unsigned char py_inb(int port) { return inb(port); }
Lancez SWIG et générez le fichier io.so
. Voici alors un example d'utilisation (rappelez vous que vous
devez lancer l'interpréteur python en tant que root
) :
>>>import io >>>io.py_iopl(3) >>>io.py_outb(10, 0x378) >>>io.py_inb(0x378) 10 >>>
On peut accéder depuis Python à des variables globales déclarées dans votre module C. Créons un module
example définissant deux variables foo
et baz
:
int foo = 10; int baz = 20;
Les variables foo
et baz
sont accessibles en Python par le biais d'un objet du nom de
cvar
:
>>>import example >>>example.cvar Global variables { foo, baz } >>>example.cvar.foo 10 >>>example.cvar.baz 20 >>>example.cvar.foo = 100 >>>example.cvar.foo 100 >>>
Accéder à des classes C++ est un peu délicat. Créons tout d'abord un fichier d'en-tête avec
une simple déclaration de classe. Appelons-le zoo.h
:
class Zoo{ int n; char animals[10][50]; public: Zoo(); void shut_up(char *animal); void display(); };
Créons maintenant un fichier d'interface zoo.i
:
%module zoo %{ #include "zoo.h" %} class Zoo{ char animals[10][50]; int n; public: Zoo(); void shut_up(char *animal); void display(); };
Générons les encapsulateurs Python en lançant la commande :
swig -python -c++ zoo.i
Voici notre fichier source zoo.cc
:
#include "zoo.h" #include <stdio.h> Zoo::Zoo() { n = 0; } void Zoo::shut_up(char *animal) { if (n < 10) { strcpy(animals[n], animal); n++; } } void Zoo::display() { int i; for(i = 0; i < n; i++) printf("%s\n", animals[i]); }
Nous créons les fichiers objet en lançant :
g++ -I/usr/include/python1.5 -c zoo.cc zoo_wrap.c
Nous finissons alors en créant le module zoo.so
:
ld -shared -o zoo.so zoo.o zoo_wrap.o /usr/lib/libg++.so.2.7.2 /usr/lib/libstdc++.so
Voici alors une session Python interactive avec notre module zoo
:
Script started on Mon Dec 13 14:31:26 1999
[pce@bhim]
~/src/writings/swig/src/shadow$ python
Python 1.5.1 (#1, Sep 3 1998, 22:51:17) [GCC 2.7.2.3] on linux-i386
Copyright 1991-1995 Stichting Mathematisch Centrum, Amsterdam
>>> import zoo
>>> dir(zoo)
['Zoo_display', 'Zoo_shut_up', '__doc__', '__file__', '__name__', 'new_Zoo']
>>> z=zoo.new_Zoo()
>>> zoo.Zoo_shut_up(z,'Tiger')
>>> zoo.Zoo_shut_up(z,'Lion')
>>> zoo.Zoo_display(z)
Tiger
Lion
>>> z2=zoo.new_Zoo()
>>> zoo.Zoo_shut_up(z2,'Python')
>>> zoo.Zoo_display(z2)
Python
>>>
Le constructeur de notre classe Zoo
a été mappé en une fonction appelée new_Zoo
. De même,
les fonctions membre shut_up
et display
ont été mappées dans Zoo_shut_up
et Zoo_display
.
Il est possible de créer des classes Python qui agissent comme des encapsulateurs autour des classes C++.
Voici une classe encapsulatrice en Python pour la classe C++ Zoo
:
from zoo import * class Zoo: def __init__(self): self.this = new_Zoo() def shut_up(self, animal): Zoo_shut_up(self.this, animal) def display(self): Zoo_display(self.this)
Nous pouvons alors facilement écrire :
>>> z = Zoo() >>> z.shut_up('Tiger') >>> z.shut_up('Lion') >>> z.display() Tiger Lion >>>
Il est même possible de demander à SWIG de générer ces classes fantômes Python automatiquement!
SWIG est un outil utile, facile à apprendre et simple d'emploi. Bien que nous n'ayons examiné SWIG que dans le cadre de scripts Python, son utilisation avec des langages tels que Perl et Tcl est très similaire. La page d'accueil de SWIG http://www.swig.org est par ailleurs la source d'information la plus complète.
L'adaptation française de ce document a été réalisée dans le cadre du Projet de traduction de la Gazette Linux.
Vous pourrez lire d'autres articles traduits et en apprendre plus sur ce projet en visitant notre site : http://www.traduc.org/Gazette_Linux.
Si vous souhaitez apporter votre contribution, n'hésitez pas à nous rejoindre, nous serons heureux de vous accueillir.