2016-07-29 30 views
0

使用具有C庫的eGige相機的控件工作時,我開始了一個cython代碼project,它帶有每種語言最好的東西。使用python-c-api調用的Cython回調段錯誤

該庫提供了一種方法來偵聽來自相機的心跳,以瞭解它是否已斷開連接。在C++類中的回調函數我已經做了,但是從這個C++類中調用一個類的python方法在我試過的所有方法中都陷入了分段錯誤

我封裝它在一個特定的C++類:

#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"); 
    } 
} 

從用Cython對象的代碼是:

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

回溯的最低層,它只是在嘗試準備參數。

#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 

我試圖讓使用_Py_BuildValue( 「(自我)」,個體經營)的參數;但是我在那裏有段錯誤

我也試圖與PyObject_CallFunctionObjArgs與在參數字段NULL,心想也許是指向「自我」已經被嵌入的方法指向一個特定的地址與此對象。但他們我在那裏有段錯誤

有人看到我的錯誤嗎?那裏有什麼東西會以不同的方式做出來?我希望這是我身邊的誤解,關於誰來做這件事。

更新 @2016年8月1日

發表意見之後適應症,兩處修改是在代碼所做的:

的所有指針存儲到PyCallback首先被存儲作爲相機的成員 cython級別:

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

即使這是segfaults的基本來源它看起來並沒有涉及當前的一個。

然後PyCallback :: execute()在C++中,我做了一些改變。閱讀有關GIL(全局解釋器鎖),併爲其添加幾個電話之後,我添加了一個檢查,可能引導到解決方案:

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); 
} 

即使我不知道該怎麼辦了呼叫,重點是_PyCallable_Check_返回false。

我還測試了使用的typedef選項,Ç函數指針具有相同段錯誤結果來調用它。

更新 @2016年8月3日

我繼續進行建議的修改。現在將cameraRemovalCallbackcdef更改爲def,並且PyCallback中的一些if報告現在可以找到該方法。在~PyCallback()中也加入了Py_XDECREF(_method),以防在構造函數中找到它。無用的try-catch也被刪除。

從參考Python's Object protocol,那DavidW提到,我檢查了*Call*組合的許多:落入段錯誤。

我認爲這個問題正在成爲,並得到一個論壇的外觀(question->答案 - >回放 - > ...)。對此我很抱歉,下次我會寫,告訴段錯誤已經解決,而且我會盡力。

+1

回調函數可以用cython代碼實現。請參閱[回調示例](https://github.com/cython/cython/blob/master/Demos/callback/cheese.pyx),[一箇舊問題](http://stackoverflow.com/questions/5242051/ cython-implementation-callbacks)和[一個老問題](http://stackoverflow.com/questions/11700501/python-cython-c-and-callbacks-calling-a-python-function-from-c-using-用Cython)。 –

+1

'PyObject_CallFunctionObjArgs'的文檔https://docs.python.org/2/c-api/object.html#c.PyObject_CallFunctionObjArgs意味着你應該傳遞一個可變數目的'PyObject *'s,然後是'NULL'。 'NULL'非常重要,因爲它告訴Python args列表已被覆蓋例如'PyObject_CallFunctionObjArgs(_method,self,NULL);' – DavidW

+1

不幸的是,你的例子不夠完整,不足以說明這是唯一的問題。 (另外:至少在提供的代碼中,'obj'實際上並不保存在'registerRemovalCallback'中的任何地方。) – DavidW

回答

1

我不看好這是唯一的問題,但是這肯定是一個問題

cameraRemovalCallback是一個cdef功能。這意味着該函數完全可以從C/Cython訪問,但無法通過Python訪問。這意味着PyObject_GetAttrString失敗(因爲cameraRemovalCallback不是Python屬性)。

您應該使用def而不是cdef來定義cameraRemovalCallback,那就是它可以通過普通的Python機制訪問。您還應該檢查PyObject_GetAttrString的結果 - 如果它返回NULL那麼它未能找到該屬性。

因此,您最終試圖將NULL作爲Python函數調用。


其他小問題:

你應該decref在~PyCallback_method

您應該不是請致電Py_InitializePy_Finalize。你似乎是從Python內部創建類,所以它不需要初始化或終結。完成一定會導致你的問題。我不認爲你需要通過self作爲參數PyObject_Call。 (雖然我可能是錯誤的)

Python C api不會引發C++異常,因此您的try{} catch(...)永遠不會捕獲任何東西。而是檢查返回值。

你需要遞減Py_BuildValue(當你完成它們時)的結果,也是PyObject_Call的結果。如果你不這樣做,你正在泄漏記憶。


下面的完整示例適用於我(使用Python 3.5 - 我無法使用早期版本輕鬆測試它)。如果它適合你,那麼你可能需要確定你的情況有什麼不同?如果它不適合你,那麼它更神祕。

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) 

注意 - 這有一個小問題。 Camera和它保存的回調形成一個參考循環,所以它們永遠不會被釋放。這會導致內存泄漏很小,但不會導致分段錯誤。

+0

我很欣賞你指出的問題,並以你提到的方式修改了代碼。但是當我嘗試調用python方法時仍然是段錯誤。 – srgblnch

+0

@srgblnch我確實添加了一個半「最小」的例子,它似乎對我有用(和代碼沒有太大區別)。我懷疑這實際上並不能解決你的問題,但它暗示着你的問題比你在這裏顯示的那個更深。 [添加評論,因爲我意識到提問者實際上沒有得到關於編輯回答的通知] – DavidW

+0

非常感謝,你已經得到了它。我已經開始閱讀並比較你的建議。我最近犯的錯誤是GIL。我已經與'Py_Initialize'一起刪除,他們認爲他們一起去。 我最終在python中看到了日誌消息,當相機從gige api拔出並報告時。我非常感謝你所做的努力。 – srgblnch