Disons que j’ai les deux signatures de fonction suivantes en C ++:
BYTE* init( BYTE* Options, BYTE* Buffer )
et:
int next( BYTE* interface, BYTE* Buffer )
L’idée est que j’initialise d’abord une classe d’ Interface
en C ++, puis que j’appelle ensuite la fonction next
depuis Python, avec une référence à cette classe.
La première fonction renvoie un pointeur BYTE
à l’interface via:
Interface* interface; // initialize stuff return((BYTE*) interface);
Je l’appelle en Python comme ceci:
class Foo: def init(self, data): # left out: setting options_ptr buf = (c_ubyte * len(data.bytes)).from_buffer_copy(data.bytes) init_fun = getattr(self.dll, '?init@@YAPAEPAE0HH@Z') init_fun.restype = POINTER(c_ubyte) self.interface_ptr = init_fun(options_ptr, buf) # this works fine! def next(self, data): # create buf from other data buf = (c_ubyte * len(data.bytes)).from_buffer_copy(data.bytes) next_fun = getattr(self.dll, '?next@@YAHPAE0HN@Z') ret = next_fun(self.interface_ptr, buf) # I randomly get segmentation faults here
J’appelle cela de l’extérieur avec, par exemple:
foo = Foo() foo.init(some_data) foo.next(some_other_data) # ... foo.next(some_additional_data)
Maintenant, quand je le lance, j’obtiens des erreurs de segmentation:
[1] 24712 segmentation fault python -u test.py
Parfois, cela se produit après le premier appel à .next()
, parfois après le onzième appel à .next()
totalement aléatoire.
Il existe un code de test C ++ pour l’API qui fonctionne de la manière suivante:
BYTE Buffer[500000]; UTIN BufSize=0; BYTE* Interface; # not shown here: fill buffer with something Interface = init(Buffer); while(true) { # not shown here: fill buffer with other data int ret = next(Interface, Buffer); }
Maintenant, comme je ne peux pas montrer le code exact, car il est beaucoup plus gros et exclusif, la question est: comment puis-je résoudre un tel problème de segmentation? Je peux rompre lorsque l’exception est levée (lors du débogage avec VS2012), mais cela rompt ici:
Clairement, cela n’est pas utile car rien n’est fait avec aucun tampon à la ligne indiquée. Et les valeurs des variables sont aussi cryptiques:
Dans mon cas, les data
sont un object BitSsortingng . Le problème pourrait-il se passer si le code C ++ effectue les opérations de mémoire sur la mémoire tampon? Ou que certaines données sont récupérées par Python quand elles sont encore nécessaires?
Plus généralement, comment puis-je m’assurer que les erreurs de segmentation ne se produisent pas lorsque je travaille avec Ctypes? Je sais que l’API DLL sous-jacente fonctionne correctement et ne plante pas.
Mise à jour: lorsque je buf
une variable d’instance, par exemple self._buf
, une erreur de segmentation se produit, mais elle se rompt à un emplacement différent lors du débogage:
J’ai eu quelques incompréhensions, ce qui a conduit aux problèmes:
Lorsque vous créez un object Ctypes dans Python et que vous le transmettez à une fonction C, et que cet object Python n’est plus nécessaire, il est (probablement) mis au rebut et ne se trouve plus dans la stack de mémoire où C s’attend à ce qu’il soit.
Par conséquent, faites du tampon une variable d’instance, par exemple self._buf
.
Les fonctions C s’attendent à ce que les données soient mutables. Si les fonctions C ne copient pas les données ailleurs, mais travaillent directement sur la mémoire tampon, celle-ci doit être mutable. La documentation Ctypes spécifie ceci:
L’affectation d’une nouvelle valeur aux instances des types de pointeurs c_char_p, c_wchar_p et c_void_p modifie l’emplacement mémoire auquel elles pointent, pas le contenu du bloc mémoire (bien sûr, car les chaînes Python sont immuables).
Veillez toutefois à ne pas les transmettre aux fonctions qui attendent des pointeurs sur la mémoire mutable. Si vous avez besoin de blocs de mémoire mutables, ctypes a une fonction
create_ssortingng_buffer()
qui les crée de différentes manières. Le contenu actuel du bloc de mémoire est accessible (ou modifié) avec la propriétéraw
; si vous voulez y accéder en tant que chaîne terminée parNUL
, utilisez la propriétévalue
:
Alors, j’ai fait quelque chose comme ça:
self._buf = create_ssortingng_buffer(500000) self._buf.value = startdata.bytes
.next()
, j’ai fait ceci: self._buf.value = nextdata.bytes
Maintenant, mon programme fonctionne comme prévu.