Search code examples
c++pythonwxpythonswigpython-c-api

Python Callback from SWIG PyObject_Call Segfault


I have a wx.py.Shell.shell widget which lets the user execute python code that interacts with my program. I want to be able to pass a function that the user defines in this space to my C++ code (Through the wxswig generated wrapper around my custom widget)and execute it.

In my C++ code I'm using a std::function <> class to invoke bound functions (C++ or Python)

So I created a simple class to wrap the PyObject with the function call operator. However I get a segfault when I try to call the PyObject *.

class PyMenuCallback
{
    PyObject *Func;
public:
    PyMenuCallback(const PyMenuCallback &op2);
    PyMenuCallback(PyObject *func);
    ~PyMenuCallback ();

    void operator() (int id);
};
/////////////////////////////////////////////////////////
PyMenuCallback::PyMenuCallback(PyObject *func)
    : Func(func)
{
    Py_XINCREF (Func);
    if(!PyCallable_Check(Func))
        cout << "Not a Callable Callback." << endl; //Throw an exception or something
}

PyMenuCallback::PyMenuCallback(const PyMenuCallback &op2)
    : Func (op2.Func)
{
    Py_XINCREF (Func);
    if(!PyCallable_Check(Func))
        cout << "Not a Callable Callback." << endl;
}

PyMenuCallback::~PyMenuCallback()
{
    Py_XDECREF (Func);
}

void PyMenuCallback::operator() (int id)
{
    cout << "Calling Callback" << endl;
    if (Func == 0 || Func == Py_None || !PyCallable_Check(Func))
        return;
    cout << "Building Args" << endl;   
    PyObject *arglist = Py_BuildValue ("(i)",id);
    cout << "Func: " << Func->ob_type->tp_name << " " << Func->ob_refcnt << endl;
    PyObject *result = PyObject_Call(Func,arglist,0); //<<<<<---SEGFAULTS HERE
    cout << "Executed" << endl;
    Py_DECREF(arglist);
    Py_XDECREF(result);
}

In my attempts to find what was going on, I put a bunch of print statements. One of which prints the type name and reference count the line before the segfault. This results in "function 3" so I have to assume the function has not been destroyed yet.

I'm passing the following to swig:

void AddOption (std::string name, PyObject *pycallback);

In which I construct a PyMenuCallback

I'm at a loss for what's causing the segfault, any ideas?


Solution

  • Since the C++ calling the python callback is within a wxWidget, and the swig wrapper is generated by the special wxPython swig (wxswig?) There is some thread protection required around the function call...

    The fixed operator should look like this

    void PyMenuCallback::operator() (int id)
    {
        cout << "Calling Callback" << endl;
        if (Func == 0 || Func == Py_None || !PyCallable_Check(Func))
            return;
        cout << "Building Args" << endl;   
        PyObject *arglist = Py_BuildValue ("(i)",id);
        cout << "Built: " << arglist << endl;
        cout << "Func: " << Func->ob_type->tp_name << " " << Func->ob_refcnt << endl;
    
        wxPyBlock_t blocked = wxPyBeginBlockThreads(); //Anti-WxSwig 
    
        PyObject *result = PyObject_Call(Func,arglist,0);
    
        wxPyEndBlockThreads(blocked);
    
    
        cout << "Executed" << endl;
        Py_XDECREF(arglist);
        Py_XDECREF(result);
    }
    

    Make sure to include

    #include "wx/wxPython/wxPython.h"
    #include "wx/wxPython/wxPython_int.h"