2017-07-23 4 views
1

Je suis très nouveau pour ctypes, et je reçois une erreur avec le programme simple ci-dessous.Pourquoi ai-je une erreur de segmentation ici? [Python ctypes]

foo.cpp

class Foo { 
public: 
    int bar; 
    Foo(int bar): bar(bar) {} 
}; 

extern "C" { 
    Foo * Foo_new(int bar) { 
     return new Foo(bar); 
    } 

    int Foo_bar(Foo *foo) { 
     return foo->bar; 
    } 
} 

foo.py

import ctypes 

libfoo = ctypes.CDLL('libfoo.so') 


class Foo: 
    def __init__(self, bar): 
     self.foo = libfoo.Foo_new(bar) 

    def bar(self): 
     return libfoo.Foo_bar(self.foo) 


foo = Foo(5) 
print(foo.bar()) 

La faute de segmentation se produit quand je l'appelle libfoo.Foo_bar(self.foo), mais selon this answer, je pensais que tout ce que je devais faire était de faire une référence forte via self.foo afin qu'il ne soit pas ramassé.

Je suppose que Foo est créé sur la pile dans le fichier CPP, il est donc immédiatement effacé après l'appel de la fonction. En tout cas, comment puis-je résoudre ce problème?

+0

ctypes n'a aucune idée de ce que l'une de ces fonctions prend ou retourne. Par exemple, il n'a aucune idée de ce qu'est un «Foo *» et aucune idée que «Foo_new» en retourne un. – user2357112

+0

La récupération de place entre en jeu pour les rappels ctypes, pas pour un pointeur vers un objet qui n'a rien à voir avec Python. C'est la segfault ici parce que le type de résultat de l'entier par défaut et la conversion de l'argument est un C 'int' de 32 bits, qui tronquera un pointeur de 64 bits. – eryksun

Répondre

3

Vous devez explicitement envelopper les arguments et les types de retour, sinon ctypes assumera des valeurs par défaut arbitraires qui pourraient ou non fonctionner. Pour envelopper le pointeur vers la classe Foo, j'utiliser un pointeur pour annuler c_void_p. Je ne sais pas si c'est la bonne approche, mais cela semble fonctionner.

import ctypes 

libfoo = ctypes.CDLL('libfoo.so') 

libfoo.Foo_new.argtypes = [ctypes.c_int] 
libfoo.Foo_new.restype = ctypes.c_void_p 

libfoo.Foo_bar.argtypes = [ctypes.c_void_p] 
libfoo.Foo_bar.restype = ctypes.c_int 

class Foo: 
    def __init__(self, bar): 
     self.foo = libfoo.Foo_new(bar) 

    def bar(self): 
     return libfoo.Foo_bar(self.foo) 

foo = Foo(5) 
print(foo.bar()) 

je l'habitude de prendre un chemin différent à l'interface avec le code C++ Python en générant une extension de python utilisant SWIG. Par conséquent, j'écrire dans un fichier test.h

class Foo { 
public: 
    int bar; 
    Foo(int bar): bar(bar) {} 
}; 

alors j'écrire un fichier d'interface SWIG test.i

%module test 
%{ 
#include "test.h" 
%} 
%include "test.h" 

J'execute dans le terminal

$ swig -python -c++ test.i 
$ c++ -fpic -shared -I/usr/include/python2.7 test_wrap.cxx -o _test.so 

Et puis je peux écrire un script Python

from test import Foo 
foo = Foo(5) 
print foo.bar 
+0

'c_void_p' n'est pas très sûr de type en général, comparé à l'utilisation d'un pointeur struct opaque. Cependant, cela n'a pas d'importance dans ce cas puisque l'instance 'foo' est une donnée encapsulée. – eryksun