2016-07-29 4 views
0

Arbeiten an einer Steuerung einer eGige Kamera, mit einer C-Bibliothek, habe ich einen cython Code project mit der Idee, die besten Dinge jeder Sprache zu haben.Cython Callback segfaults mit Python-c-Api Anrufe

Die Bibliothek bietet eine Möglichkeit, einen Herzschlag von der Kamera zu hören, um festzustellen, ob die Verbindung getrennt wurde. Der Callback mit in einer C++ - Klasse habe ich bereits getan, aber von dieser C++ - Klasse, rufen Sie eine Python-Methode einer Klasse in eine Segmentierungsfehler in allen Möglichkeiten, die ich versucht habe.

Ich habe es in einer bestimmten C++ Klasse gekapselt:

#include <Python.h> 
/* (...) */ 
PyCallback::PyCallback(PyObject* self, const char* methodName) 
{ 
    Py_XINCREF(self); 
    _self = self; 
    _method = PyObject_GetAttrString(self, methodName); 
} 
PyCallback::~PyCallback() 
{ 
    Py_XDECREF(_self); 
} 
void PyCallback::execute() 
{ 
    try 
    { 
    PyObject *args = PyTuple_Pack(1,_self); 
    PyObject_CallFunctionObjArgs(_method, args); 
    }catch(...){ 
    _error("Exception calling python"); 
    } 
} 

Von einem Cython der Code-Objekt ist:

cdef class Camera(...): 
    # (...) 
    cdef registerRemovalCallback(self): 
     cdef: 
      PyCallback* obj 
     obj = new PyCallback(<PyObject*> self, <char*> "cameraRemovalCallback") 
    cdef cameraRemovalCallback(self): 
     self._isPresent = False 

Der unterste Teil des Backtrace ist es nur, wenn versuchen, die Vorbereitung Argumente.

#0 0x00007ffff7b24592 in PyErr_Restore() from /usr/lib64/libpython2.6.so.1.0 
#1 0x00007ffff7b23fef in PyErr_SetString() from /usr/lib64/libpython2.6.so.1.0 
#2 0x00007ffff7b314dd in ??() from /usr/lib64/libpython2.6.so.1.0 
#3 0x00007ffff7b313ca in ??() from /usr/lib64/libpython2.6.so.1.0 
#4 0x00007ffff7b316c1 in ??() from /usr/lib64/libpython2.6.so.1.0 
#5 0x00007ffff7b31d2f in ??() from /usr/lib64/libpython2.6.so.1.0 
#6 0x00007ffff7b31e9c in Py_BuildValue() from /usr/lib64/libpython2.6.so.1.0 
#7 0x00007ffff637cbf8 in PyCallback::execute (this=0x16212a0) at pylon/PyCallback.cpp:53 
#8 0x00007ffff6376248 in CppCamera::removalCallback (this=0x161fb30, pDevice=<value optimized out>) at pylon/Camera.cpp:387 

Ich habe versucht, die Argumente mit _Py_BuildValue ("(Selbst-)", Selbst-) zu machen; aber dann habe ich eine segfault dort. mit in diesem Objekt

Ich habe auch versucht, mit PyObject_CallFunctionObjArgs mit NULL im Argumente-Feld, zu denken, dass vielleicht der Zeiger auf „Selbst“ ist bereits als Methode Punkt zu einer bestimmten Adresse eingebettet. Aber sie habe ich die segfault dort.

Wer sieht meinen Fehler? Etwas, das anders gemacht werden soll? Ich hoffe, dass dies ein Missverständnis von mir ist, wer das tun soll.

aktualisieren @2016.08.01:

Folgende Kommentare Indikationen sind zwei Änderungen im Code gemacht:

Zunächst wird der Zeigerspeicher zum PyCallback gespeichert worden ist als Mitglied der Kamera cython Klasse:

cdef class Camera(...): 
    cdef: 
     #(...) 
     PyCallback* _cbObj 
    # (...) 
    cdef registerRemovalCallback(self): 
     self._cbObj = new PyCallback(<PyObject*> self, <char*> "cameraRemovalCallback") 
    cdef cameraRemovalCallback(self): 
     self._isPresent = False 

Auch dies ist eine grundlegende Quelle von segfaults es sieht aus, es war nicht in der aktuellen beteiligt.

Dann PyCallback :: execute() in der C++, habe ich einige Änderungen vorgenommen. Nach der Lektüre über die GIL (Global Interpreter Lock) und einige Anrufe für sie hinzufügen, habe ich einen Scheck hinzugefügt, die zur Lösung führen kann:

PyCallback::PyCallback(PyObject* self, const char* methodName) 
{ 
    Py_Initialize(); 
    Py_XINCREF(self); 
    _self = self; 
    _method = PyObject_GetAttrString(self, methodName); 
} 

PyCallback::~PyCallback() 
{ 
    Py_XDECREF(_self); 
    Py_Finalize(); 
} 

void PyCallback::execute() 
{ 
    PyGILState_STATE gstate; 

    gstate = PyGILState_Ensure(); 
    try 
    { 
    if (PyCallable_Check(_method)) 
    { 
     _info("Build arguments and call method"); 
     PyObject *args = Py_BuildValue("(O)", _self); 
     PyObject *kwargs = Py_BuildValue("{}", "", NULL); 
     PyObject_Call(_method, args, kwargs); 
    } 
    else 
    { 
     _warning("The given method is not callable!"); 
    } 
    } 
    catch(...) 
    { 
    // TODO: collect and show more information about the exception 
    _error("Exception calling python"); 
    } 
    PyGILState_Release(gstate); 
} 

Auch ich bin nicht sicher, wie der Anruf zu tun Der Hauptpunkt ist, dass _PyCallable_Check_ false zurückgibt.

Ich habe auch zu verwenden, die typedef Option und C Zeiger auf Funktion nennen es mit dem gleichen segfault Ergebnis getestet.

aktualisieren @2016.08.03:

Ich habe mit den vorgeschlagenen Änderungen gehen. cameraRemovalCallback wird jetzt von cdef zu def geändert und einige if s in PyCallback meldet, dass die Methode jetzt gefunden wird. Auch wurde ~PyCallback() ein Aufruf an Py_XDECREF(_method) hinzugefügt, falls es im Konstruktor gefunden wurde. Der nutzlose try-catch wurde ebenfalls entfernt.

Aus dem Verweis auf die , die DavidW Erwähnung, habe ich viele der *Call* Kombinationen überprüfen: auf den Segfault fallen.

Ich denke, diese Frage schmutzig immer und wird immer das Aussehen eines Forum (Frage-> Antwort-> Replay -> ...). Es tut mir leid, und ich werde versuchen, das nächste Mal, wenn ich schreibe, sagen, dass der Segfault gelöst wurde und was genau war.

+1

Callback-Funktionen können in Cython-Code implementiert werden. Siehe [ein Rückrufbeispiel] (https://github.com/cython/cython/blob/master/Demos/callback/cheese.pyx), [eine alte Frage] (http://stackoverflow.com/questions/5242051/ cython-implementation-callbacks) und [eine alte Frage] (http://stackoverflow.com/questions/11700501/python-cython-c-and-callbacks-calling-a-python-function-from-cusing-) Cython). –

+1

Die Dokumentation für 'PyObject_CallFunctionObjArgs' https://docs.python.org/2/c-api/object.html#c.PyObject_CallFunctionObjArgs impliziert, dass Sie eine variable Anzahl von' PyObject * 's gefolgt von' NULL' übergeben sollten. Das 'NULL' ist wichtig, weil es Python mitteilt, dass die Liste der Argumente overs ist. Beispiel:' PyObject_CallFunctionObjArgs (_method, self, NULL); ' – DavidW

+1

Leider ist Ihr Beispiel nicht vollständig genug, um zu sagen, ob das das einzige Problem ist. (Außerdem: 'obj' ist zumindest im angegebenen Code nicht irgendwo in' registerRemovalCallback' gespeichert.) – DavidW

Antwort

1

Ich bin nicht diese viel versprechende ist das einzige Problem, aber das ist sicherlich ein Ausgabe:

cameraRemovalCallback ist eine cdef Funktion. Dies bedeutet, dass die Funktion rein von C/Cython zugänglich ist, aber von Python aus nicht zugänglich ist. Dies bedeutet, dass PyObject_GetAttrString fehlschlägt (seit cameraRemovalCallback ist kein Python-Attribut).

Sie sollten cameraRemovalCallback mit def anstelle von cdef definieren und das war es wird durch normale Python-Mechanismen zugänglich sein. Sie sollten auch das Ergebnis von PyObject_GetAttrString überprüfen - wenn es NULL zurückgibt, konnte es das Attribut nicht finden.

Daher versuchen Sie am Ende, NULL als eine Python-Funktion aufzurufen.


Andere kleinere Probleme:

Sie sollten _method in ~PyCallback decref.

Sie sollten nicht Anruf Py_Initialize und Py_Finalize. Sie scheinen die Klasse trotzdem in Python zu erstellen, so dass sie nicht initialisiert oder finalisiert werden muss. Das Finalisieren wird dir definitiv Probleme bereiten.

Ich glaube nicht, dass Sie self als Argument an PyObject_Call übergeben müssen. (Ich könnte aber falsch liegen)

Die Python C API wird C++ Ausnahmen nicht auslösen, so dass Ihre try{} catch(...) nie etwas fangen wird. Überprüfen Sie stattdessen die Rückgabewerte.

Sie müssen die Ergebnisse beider Aufrufe von Py_BuildValue (wenn Sie damit fertig sind) auch das Ergebnis von PyObject_Call dekretieren. Wenn Sie das nicht tun, verlieren Sie Speicher.

Das folgende vollständige Beispiel funktioniert für mich (mit Python 3.5 - ich kann es nicht einfach mit früheren Versionen testen). Wenn es für Sie funktioniert, müssen Sie wahrscheinlich herausfinden, was in Ihrem Fall anders ist. Wenn es für dich nicht funktioniert, ist es mysteriöser.

pycallback.hpp:

#include <Python.h> 
#include <stdexcept> 

inline PyObject* getCallable(PyObject* o, const char* methodName) { 
    // assume o is not null 
    PyObject* callable = PyObject_GetAttrString(o,methodName); 
    if (callable == nullptr) { 
     throw std::runtime_error("Attribute does not exist"); 
    } 
    return callable; 
} 

class PyCallback { 
private: 
    PyObject* _callable; 

public: 
    PyCallback(PyObject* callable) { 
     // assume callable isn't null 
     if (!PyCallable_Check(callable)) { 
      throw std::runtime_error("object passed to PyCallback is not callable"); 
     } 
     _callable = callable; 
     Py_XINCREF(_callable); 
    } 

    PyCallback(PyObject* o, const char* methodName) : 
    PyCallback(getCallable(o,methodName)) { // needs C++11 to compile 
    } 

    // don't define copy operators 
    PyCallback(const PyCallback&) = delete; 
    PyCallback& operator=(const PyCallback&) = delete; 

    ~PyCallback() { 
     Py_XDECREF(_callable); 
    } 

    void execute() { 
     PyGILState_STATE gstate; 
     gstate = PyGILState_Ensure(); 

     PyObject* result = PyObject_CallFunctionObjArgs(_callable,nullptr); 

     Py_XDECREF(result); // allowed to be null 
     PyGILState_Release(gstate); 
    } 
}; 

camera.pyx

cdef extern from "pycallback.hpp": 
    cdef cppclass PyCallback: 
     PyCallback(object) except + 
     PyCallback(object, const char*) except + 
     void execute() 

cdef class Camera: 
    cdef PyCallback* o 
    cdef public ispresent 

    def __init__(self): 
     self.o = NULL 
     self.ispresent = True 

    def registerRemovalCallback(self): 
     self.o = new PyCallback(self,'cameraRemovalCallback') 
     #self.o = new PyCallback(self.cameraRemovalCallback) 

    def cameraRemovalCallback(self): 
     self.ispresent = False 

    def triggerCallback(self): 
     if self.o != NULL: 
      self.o.execute() 

setup.py

from distutils.core import setup 
from distutils.extension import Extension 
from Cython.Distutils import build_ext 

setup(
    ext_modules = [ 
     Extension('camera',sources=["camera.pyx"], 
      language="c++", 
      extra_compile_args=['-std=c++11'])], 
    cmdclass={'build_ext': build_ext}) 

test.py

import camera 

c = camera.Camera() 
print(c.ispresent) 
c.triggerCallback() 
print(c.ispresent) 
c.registerRemovalCallback() 
print(c.ispresent) 
c.triggerCallback() 
print(c.ispresent) 

Hinweis - es gibt ein kleines Problem damit. Camera und der Callback, den es enthält, bilden eine Referenzschleife, sodass sie niemals freigegeben werden. Dies führt zu einem kleinen Speicherverlust, aber es wird kein Segmentierungsfehler verursacht.

+0

Ich schätze die Probleme, auf die Sie hingewiesen haben, und ich habe den Code so geändert, wie Sie es erwähnt haben. Aber immer noch ein segfault, wenn ich versuche, die Python-Methode aufzurufen. – srgblnch

+0

@srgblnch Ich habe ein halb- "minimales" Beispiel hinzugefügt, das für mich zu funktionieren scheint (und sich nicht sehr von Ihrem Code unterscheidet). Ich vermute, dass dies Ihr Problem nicht wirklich löst, aber es deutet darauf hin, dass Ihr Problem tiefer in Ihrem Code liegt als das, was Sie hier gezeigt haben. [Kommentar hinzugefügt, weil ich merke, dass die Fragesteller nicht über Änderungen an den Antworten informiert werden] – DavidW

+0

Vielen Dank, du hast es. Ich fange an zu lesen und zu vergleichen, was Sie vorschlagen. Der letzte Fehler, den ich gemacht habe, war mit der GIL. Ich habe zusammen mit dem 'Py_Initialize' entfernt, dass sie denken, dass sie zusammen gehen. Endlich sah ich die Log-Nachricht in Python, wenn eine Kamera aus der Gige API ausgesteckt und gemeldet wurde. Ich schätze Ihre Bemühungen sehr. – srgblnch