Hi all!
I am writing a partial wrapper around sundials (and ODE solver written in C) for a project of mine and
I wanted to be able to pass python callbacks to the C library using Cython (even though
I mainly pass C pointers to native code).
It was not immediately apparent how I should go about achieving this but I ended up using
a global variable in the Cython module to point to the user provided Python callback.
A global pointer will obviously cause problems as soon as you need to register multiple/variable callback functions. What you really want is a "closure" in C, which doesn't exist. Newer versions of C++ have them. It might be
interesting to see if such a closure can be used as a function pointer where a plain C function is expected.
There is a usual work-around in C for cases where closures would likely be useful, though: In many C interfaces where one specifies a callback, one not only gives a function pointer but also a (void) pointer to "user data". This pointer gets passed back to the callback function as a parameter when the callback happens. This is what you can use to pass variable data back to your own function.
In your case, the pointer to the python callback could be stored in the "user data" and your C-level specified callback function can lookup this pointer and call the python function as desired.
cdef ... callback( ...parameters ..., void * userdata):
F=<PyObject *>userdata
python_result = F( ... processed parameters ...)
return <processed python result to match callback signature>
register_callback( &callback, pointer_to_python_object_specified_as_userdata)
It looks like the SUNDIALS API indeed has room for userdata pointers in its callback signatures.
Even though global pointers aren't pretty there is only one per "callback wrapper" so I am not sure what you mean that they will cause
problems for mulitple/variable callback functions?
I am actually writing a C++ wrapper foremost so I don't want to reference
any Python related objects in the C++ wrapper. Using Cython to expose a C function pointer interface to a Python
callback lets me use the wrapper unmodified.
If you have multiple instances of whatever object these callbacks get registered with, then using the same "callback wrapper" for each instance would be the natural thing to do, but with a global pointer you cannot do that. Perhaps that doesn't happen for and/or perhaps SUNDIALS has been designed in a way that prevents that from ever happening (fortran based code perhaps?). However, a general example for how to wrap callback registration should definitely keep this in mind.
The "void *user_data" really is there to help you avoid global variables. All well-designed C callback interfaces have them (as a functional equivalent to closures). Your scenario is exactly what they are for.
PyObject * x_ = PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, (void *)x);
But it is segfaulting in my invocation of:PyObject * x_ = PyArray_SimpleNewFromData(1, dims, NPY_DOUBLE, (void *)x);