Utiliser SWIG pour l'interfaçage des langages de scripts avec le C/C++

Gazette Linux n°49 — Janvier 2000

Pierre Tane

Adaptation française 

Frédéric Marchal

Correction du DocBook 

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

Introduction
Téléchargement et installation de SWIG
Un exemple basique
Est-ce que C apporte un gain de vitesse  ?
Comment bien utiliser SWIG ?
Un exemple d'interface de bas niveau
Accéder aux variables C
Accéder à des classes C++
Classes fantômes
Conclusion

Introduction

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.

Téléchargement et installation de SWIG

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

Un exemple basique

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
>>>

Est-ce que C apporte un gain de vitesse  ?

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

Comment bien utiliser SWIG ?

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.

Un exemple d'interface de bas niveau

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
>>>

Accéder aux variables C

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++

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.

Classes fantômes

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!

Conclusion

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.

Adaptation française de la Gazette Linux

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.