Recherche…


Introduction

ctypes est une bibliothèque intégrée python qui appelle les fonctions exportées à partir de bibliothèques compilées natives.

Remarque: Comme cette bibliothèque gère le code compilé, il est relativement dépendant du système d'exploitation.

Utilisation de base

Disons que nous voulons utiliser la fonction ntohl libc .

Tout d'abord, nous devons charger libc.so :

>>> from ctypes import *
>>> libc = cdll.LoadLibrary('libc.so.6')
>>> libc
<CDLL 'libc.so.6', handle baadf00d at 0xdeadbeef>

Ensuite, nous obtenons l'objet function:

>>> ntohl = libc.ntohl
>>> ntohl
<_FuncPtr object at 0xbaadf00d>

Et maintenant, nous pouvons simplement invoquer la fonction:

>>> ntohl(0x6C)
1811939328
>>> hex(_)
'0x6c000000'

Ce qui fait exactement ce que nous attendons de lui.

Pièges communs

Ne pas charger un fichier

La première erreur possible échoue lors du chargement de la bibliothèque. Dans ce cas, une erreur OSE est généralement déclenchée.

C'est soit parce que le fichier n'existe pas (ou ne peut pas être trouvé par le système d'exploitation):

>>> cdll.LoadLibrary("foobar.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
    return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: foobar.so: cannot open shared object file: No such file or directory

Comme vous pouvez le voir, l'erreur est claire et assez indicative.

La deuxième raison est que le fichier est trouvé mais n’a pas le bon format.

>>> cdll.LoadLibrary("libc.so")
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 425, in LoadLibrary
    return self._dlltype(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 347, in __init__
    self._handle = _dlopen(self._name, mode)
OSError: /usr/lib/i386-linux-gnu/libc.so: invalid ELF header

Dans ce cas, le fichier est un fichier script et non un fichier .so . Cela peut également se produire lorsque vous essayez d'ouvrir un fichier .dll sur une machine Linux ou un fichier 64 bits sur un interpréteur Python 32 bits. Comme vous pouvez le voir, dans ce cas, l'erreur est un peu plus vague et nécessite des fouilles.

Ne pas accéder à une fonction

En supposant que nous avons chargé avec succès le fichier .so , nous devons ensuite accéder à notre fonction comme nous l'avons fait sur le premier exemple.

Lorsqu'une fonction non existante est utilisée, un AttributeError est déclenché:

>>> libc.foo
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.5/ctypes/__init__.py", line 360, in __getattr__
    func = self.__getitem__(name)
File "/usr/lib/python3.5/ctypes/__init__.py", line 365, in __getitem__
    func = self._FuncPtr((name_or_ordinal, self))
AttributeError: /lib/i386-linux-gnu/libc.so.6: undefined symbol: foo

Objet ctypes de base

L'objet le plus fondamental est un int:

>>> obj = ctypes.c_int(12)
>>> obj
c_long(12)

Maintenant, obj réfère à un morceau de mémoire contenant la valeur 12.

Cette valeur est accessible directement et même modifiée:

>>> obj.value
12
>>> obj.value = 13
>>> obj
c_long(13)

Depuis obj réfère à une partie de la mémoire, nous pouvons également trouver sa taille et son emplacement:

>>> sizeof(obj)
4
>>> hex(addressof(obj))
'0xdeadbeef'

tableaux de type ctypes

Comme tout bon programmeur C le sait, une seule valeur ne vous mènera pas aussi loin. Ce qui va vraiment nous faire avancer, ce sont les tableaux!

>>> c_int * 16
<class '__main__.c_long_Array_16'>

Ce n'est pas un tableau réel, mais c'est assez proche! Nous avons créé une classe qui dénote un tableau de 16 int s.

Il ne reste plus qu'à l'initialiser:

>>> arr = (c_int * 16)(*range(16))
>>> arr
<__main__.c_long_Array_16 object at 0xbaddcafe>

Maintenant, arr est un tableau qui contient les nombres de 0 à 15.

Ils sont accessibles comme n'importe quelle liste:

>>> arr[5]
5
>>> arr[5] = 20
>>> arr[5]
20

Et tout comme n'importe quel autre objet ctypes , il a également une taille et un emplacement:

>>> sizeof(arr)
64 # sizeof(c_int) * 16
>>> hex(addressof(arr))
'0xc000l0ff'

Fonctions d'emballage pour les types

Dans certains cas, une fonction C accepte un pointeur de fonction. En tant ctypes avides de ctypes , nous aimerions utiliser ces fonctions, et même passer la fonction python en argument.

Définissons une fonction:

>>> def max(x, y):
        return x if x >= y else y

Maintenant, cette fonction prend deux arguments et renvoie un résultat du même type. Pour l'exemple, supposons que ce type est un int.

Comme nous l'avons fait sur l'exemple du tableau, nous pouvons définir un objet qui dénote ce prototype:

>>> CFUNCTYPE(c_int, c_int, c_int)
<CFunctionType object at 0xdeadbeef>

Ce prototype dénote une fonction qui retourne un c_int (le premier argument) et accepte deux arguments c_int (les autres arguments).

Maintenant, enveloppons la fonction:

>>> CFUNCTYPE(c_int, c_int, c_int)(max)
<CFunctionType object at 0xdeadbeef>

Les prototypes de fonctions ont plus d'usage: ils peuvent envelopper la fonction ctypes (comme libc.ntohl ) et vérifier que les arguments corrects sont utilisés lors de l'appel de la fonction.

>>> libc.ntohl() # garbage in - garbage out
>>> CFUNCTYPE(c_int, c_int)(libc.ntohl)()
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
TypeError: this function takes at least 1 argument (0 given)

Utilisation complexe

lfind tous les exemples ci-dessus en un scénario complexe: utiliser la fonction lfind libc .

Pour plus de détails sur la fonction, lisez la page de manuel . Je vous exhorte à le lire avant de continuer.

Tout d'abord, nous définirons les prototypes appropriés:

>>> compar_proto = CFUNCTYPE(c_int, POINTER(c_int), POINTER(c_int))
>>> lfind_proto = CFUNCTYPE(c_void_p, c_void_p, c_void_p, POINTER(c_uint), c_uint, compar_proto)

Ensuite, créons les variables:

>>> key = c_int(12)
>>> arr = (c_int * 16)(*range(16))
>>> nmemb = c_uint(16)

Et maintenant, nous définissons la fonction de comparaison:

>>> def compar(x, y):
        return x.contents.value - y.contents.value

Notez que x et y sont POINTER(c_int) , nous devons donc les déréférencer et prendre leurs valeurs afin de comparer la valeur stockée dans la mémoire.

Maintenant, nous pouvons tout combiner:

>>> lfind = lfind_proto(libc.lfind)
>>> ptr = lfind(byref(key), byref(arr), byref(nmemb), sizeof(c_int), compar_proto(compar))

ptr est le pointeur vide retourné. Si la key n'a pas été trouvée dans arr , la valeur serait None , mais dans ce cas, nous avons une valeur valide.

Maintenant, nous pouvons le convertir et accéder à la valeur:

>>> cast(ptr, POINTER(c_int)).contents
c_long(12)

En outre, nous pouvons voir que ptr pointe vers la valeur correcte à l'intérieur d' arr :

>>> addressof(arr) + 12 * sizeof(c_int) == ptr
True


Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow