Search code examples
pythonc++callbackpython-c-api

Strange behaviour when calling python functions from cython c


I have some C++ code, some python code and some cython code. In C++, I have an asynchronous callback that gets executed, and I want python code to be executed.

Here's what I did. In python, I wrote a function that took 2 parameters:

def fn(x,y):
    print("hello")
    print(x)
    print(y)

Then, in C++, I wanted to call this function "fn" asynchronously as a callback. So I created a C++ function called "PythonCallback" to wrap the callback.

class PythonCallback{
public:
    PyObject *_pyfunc;

    PythonCallback(PyObject *pyfunc);
    void presentCallback(_bstr_t EventName, BYTE* CallbackData);
    void addCallbackToGUID( PyObject* Py_GUID );
}


//Constructor
PythonCallback::PythonCallback(PyObject *pyfunc){
if( PyCallable_Check(pyfunc)){
    Py_INCREF(pyfunc);
    _pyfunc = pyfunc;
}else{
    throw -1;
}
};

void PythonCallback::presentCallback(_bstr_t EventName, BYTE* pCallbackData)
{
PyGILState_STATE gstate;
gstate = PyGILState_Ensure();
EventData* evData = (EventData*)pCallbackData;
PyObject *args = Py_BuildValue("(ss)", EventName, EventName);
const wchar_t* wstring(EventName);
PyObject_CallObject(_pyfunc, args );

std::wstring w;
w.append(EventName);
//    PyObject_CallFunction( _pyfunc, "(ss)", wstring, wstring);
//    PyObject_CallFunction( _pyfunc, "(ss)", w.c_str(),w.c_str());
//    PyObject_CallFunction( _pyfunc, "(s)", w.c_str() );
//    PyObject_CallFunction( _pyfunc, "" );
//    PyObject_CallFunction( _pyfunc, "ss", wstring, wstring );

PyGILState_Release(gstate);

};

And all of this is glued together with Cython. In Cython, I created a class that would send the python function to C++

cdef class CallbackInformation:
    cdef PythonCallback *thisptr
    def __cinit__(self, object func):
        self.thisptr = new PythonCallback(func)
        # temp = PythonCallback(func)

    def __dealloc__(self):
        del self.thisptr

    def addCallbackToGUID(self,guid):
        self.thisptr.addCallbackToGUID(guid)

So, then what I would do is create a new instance of CallbackInformation and pass it the fn. i.e. instance = CallbackInformation(fn)

The problem happens when I call the python callback function fn. I don't get an immediate error, but when I try to just check fn in the python console, i.e. if I just type fn in the console, I get the following error:

File "C:/Users/eric/PycharmProjects/SuperResolution/Startup3dCalibration.py", >line 62, in fn print("hello") UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf8 in position 0: invalid >start byte

if I do it again, I get his message

hellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohellohello

and finally, if I do it a third time, I get the expected output: <function fn at 0x0000000002281048>

Where did I go wrong?


Solution

  • Yet again, the solution comes right after you post the question:

    so I realized that with Py_BuildValue, there's a fundamental difference between a char * string and a unicode string. So, simply replacing PyObject *args = Py_BuildValue("(ss)", EventName, EventName); with PyObject *args = Py_BuildValue("(uu)", EventName, EventName); fixed my problem.

    I guess the error about unconvertable unicode makes sense in the end.