Sam & Max » so http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Appeler du code C depuis Python avec ctypes 28 http://sametmax.com/appeler-du-code-c-depuis-python-avec-ctypes/ http://sametmax.com/appeler-du-code-c-depuis-python-avec-ctypes/#comments Sun, 05 May 2013 06:55:40 +0000 http://sametmax.com/?p=5811 On vous a dit et répété que Python c’était un super langage de glu et que ça pouvait très facilement s’interfacer avec les binaires produits par du C. Mais jusqu’à quel point ?

En vérité c’est extrêmement simple : Python permet de se mapper directement sur un .dll ou un .so, et d’appeler n’importe quelle fonction qu’il contient depuis le code Python comme si c’était une fonction normale. Il n’y a rien à installer, c’est fourni d’office.

On peut tester ça facilement. Je ponds une lib d’une folle puissance grâce à mes talents de codeurs C internationalement connus dans le quartier :

#include<stdio.h>
 
/*
Attend un pointeur sur un array de caractères (une chaîne en C) 
et l'affiche.
*/
dit_papa(char * p)
{
    printf("%s\n", p);
}
 
 
/*
Attend deux entiers et les multiple
*/
multiplier(long a, long b)
{
    return a * b;
}
 
/*
Attend un pointeur de pointeur sur un array de char
parce qu'on aime les risques.
*/
jakadi(char ** p)
{
    printf("%s\n", *p);
}

On compile tout ça. Comme je suis sous Nunux, j’utilise GCC et j’obtiens un .so, mais sous Windows c’est pareil avec VisualStudio et les .dll.

gcc -shared -Wl,-soname,ZeLib -o ZeLib.so -fPIC ZeLib.c

ZeLib.so est prête et frétille d’impatience à l’idée de vous servir. Il ne reste plus qu’à lancer le shell Python…

D’abord on fait ses imports, c’est dans la lib standard tout ça :

>>> import ctypes

Ensuite on se bind sur le binaire, il faut préciser un chemin absolu sinon ça ne marche pas :

>>> zelib = ctypes.CDLL("/home/sam/Work/projet/ZeLib.so")

Et derrière on peut appeler une fonction (Python fait la conversion entre tous les types de bases Python et C) :

>>> res = zelib.multiplier(2, 3)
>>> print res
6

Si on veut faire des chaînes, on ne peut pas passer de l’unicode. Comme mon shell a toutes les chaînes en unicode par défaut, je dois encoder dans le charset de sortie (sur mon système, c’est UTF8):

>>> zelib.dit_papa("papa".encode('utf8'))
papa
5

Notez au passage que la fonction retourne quelque chose même si je n’ai pas précisé de valeur de retour. Du coup j’aurais mieux fait de mettre un bon return 0 à la fin.

Si on veut appeler une fonction qui attend un pointeur, il faut d’abord caster son type en un type C, puis appeler by_ref dessus, qui va passer l’argument par référence, plutôt que par valeur :

>>> from ctypes import *
>>> s = ctypes.c_char_p('kiwi'.encode('utf8'))
>>> zelib.jakadi(byref(s))
kiwi
5

Voilà.

Voilà, voilà.

Bon attention quand même, le C n’est pas aussi conciliant que le Python : le debug est plus dur (pas de stacktrace, mais un bon vieux core dump), pas de completion dans ipython, et on peut même planter la VM si on se débrouille bien :-) N’oubliez pas non plus que Python 64 bits ne peut pas taper dans des DLL 32 bits et inversement.

P.S: je ne vais pas mettre le code C à télécharger et le code Python, franchement, il est pas énorme. Donc petite exception dans cet article : y a rien à DL et la syntaxe est pas à base de comments.

]]>
http://sametmax.com/appeler-du-code-c-depuis-python-avec-ctypes/feed/ 28