Utilisation de Ctypes #
ctypes
est un module Python, de la bibliothèque standard, qui permet,
depuis un programme Python, d’appeler des fonctions et procédures
présentes dans une bibliothèques compilées, généralement écrire en C
(.dll
sous Windows et .so
sous Linux).
Documentation de ctypes : https://docs.python.org/3.4/library/ctypes.html
Cette documentation est succincte mais elle présente quelques exemples d’utilisation très simples.
Vous avez aussi une vidéo explicative (un peu ancienne) :
Écriture des fonctions en C #
L’objectif de ctypes
étant en particulier de pouvoir utiliser des
bibliothèques déjà compilées, il n’y a rien de particulier à faire
pour écrire les fonctions C. Elles sont en C standard, sans déclaration
particulière et n’utilisent pas de librairie spécifique.
Voici un exemple de bibliothèque, que nous appellerons tools
et qui
contient quelques fonctions simples illustrant les propos qui suivent :
#include <stdio.h>
#include <stdlib.h>
void hello() {
printf("Hello World\n");
}
int ajoute_entiers(int a, int b) {
return a + b;
}
double moyenne(double a, double b) {
return (a + b)/2;
}
double moyenne_tableau(int * tab, int n) {
int i=0;
double s = 0;
for (i=0; i<n; i++){
s += tab[i];
}
return s/n;
}
Récupérez ce fichier et compilez le sous forme d’une bibliothèque dynamique.
Compilation sous Unix :
$ gcc -shared -O2 -o tools.so -fPIC tools.c
Compilation sous Windows : Il faut construire une DLL (Shared Library), ce qui se fait sans difficulté avec Code::Blocks. Veillez toutefois, avec une version Python 64 bits, de compiler avec un mingw 64 bits (installé dans L:) (http://blog.security-helpzone.com/programmation/programmer-en-64x-avec-codeblocks.html)
Utilisation de la bibliothèque en Python #
Dans une session shell, testez :
>>> import ctypes as ct
>>> _lib = ct.cdll.LoadLibrary("./tools.so")
>>> _lib
<CDLL './tools.so', handle 202c9d0 at 7f1e6745c940>
>>> _lib.hello()
Hello World
12
>>> _lib.ajoute_entiers()
-2004708440
>>> _lib.ajoute_entiers(2,3)
5
>>> _lib.ajoute_entiers(2147483647,0) # Taille maxi d'un int 32 bits
2147483647
>>> _lib.ajoute_entiers(2147483647,2) # Oups...
-2147483647
>>> _lib.ajoute_entiers('ha! ha!','hello','world')
71587248
Si la fonction est utilisée correctement et ne prend en paramètre que des entiers, le résultat est OK. Sinon…
Il est possible de déclarer, côté Python, les types attendus :
>>> _lib.ajoute_entiers.argtypes = [ct.c_int, ct.c_int]
>>> _lib.ajoute_entiers('ha! ha!','hello','world')
ArgumentError: argument 1: <class 'TypeError'>: wrong type
Aaaaahhhh!!!!!
On peut de même annoncer le type de retour :
>>> _lib.ajoute_entiers.restype = ct.c_int
Maintenant, les fonctions se comportent un peu plus sagement.
Voici un exemple de programme Python qui utilise correctement la bibliothèque C :
import ctypes as ct
_lib = ct.cdll.LoadLibrary("./tools.so")
_lib.moyenne.argtypes = [ct.c_double, ct.c_double]
_lib.moyenne.restype = ct.c_double
_lib.ajoute_entiers.argtypes = [ct.c_int, ct.c_int]
_lib.ajoute_entiers.restype = ct.c_int
_lib.moyenne_tableau.argtypes = [ct.POINTER(ct.c_int), ct.c_int]
_lib.moyenne_tableau.restype = ct.c_double
# Utilisation
r2 = _lib.moyenne_tableau((ct.c_int * 4)(3,1,6,11), 4)
print(r2)
Utilisation avec NumPy #
Une des raisons pour lesquels on voudrait pouvoir écrire du C (et donc
utiliser ctypes
) est l’optimisation de traitement coûteux contenant
des boucles, comme des traitements sur les images.
Partant du principe que, côté Python, les images sont représentées par des tableaux NumPy, on cherche à écrire une fonction C qui prend un tableau NumPy en paramètre (un tableau tridimensionnel, puisque c’est une image).
Numpy propose quelques fonctions facilitant la conversation avec le C.
Supposons que nous ayons la fonction suivante qui assombrit une image
(supposons que nous ne sachions pas que img2 = img1 // 3
fonctionnerait… ) :
def sombre(img1):
img2 = img1.copy()
mi, mj = img1.shape[:2]
for i in range(mi):
for j in range(mj):
for k in range(3):
img2[i,j,k] = img1[i,j,k] // 3
return img2
La fonction précédente est très lente.
Nous écrirons son équivalent en C (à ajouter dans tools.c
):
void sombre(unsigned char *i1, int mi, int mj, unsigned char* i2) {
int i,j,k;
int v;
for (i=0;i<mi;i++) {
for(j=0;j<mj;j++) {
for(k=0;k<3;k++) {
v = k+3*(i*mj+j);
i2[v] = i1[v]/3;
}
}
}
}
Cela nécessite un peut d’arithmétique sur les pointeurs, et il faut savoir à quel type de données correspond une composante et comment sont agencés les pixels de l’image.
Nous écrirons ensuite en Python :
import numpy as np
import ctypes as ct
_lib = ct.cdll.LoadLibrary("./tools.so")
_lib.sombre.argtypes = [np.ctypeslib.ndpointer(dtype = np.uint8),
ct.c_int, ct.c_int,
np.ctypeslib.ndpointer(dtype = np.uint8)]
_lib.sombre.restype = ct.c_void_p
def sombre(img1):
img2 = img1.copy()
mi, mj = img1.shape[:2]
_lib.sombre(img1, mi, mj, img2)
return img2
Le temps de calcul est typiquement divisé par plus de 100….