I'm currently porting a C library to python using pybind11, and it has lots of C-style function pointers (i.e. not std::function, as described in https://pybind11.readthedocs.io/en/stable/advanced/cast/functional.html)
So my question is: is there an easy way to handle C style function pointers, when they are passed a function parameters?
For example I need to bind this function type (as an illustration, I took function pointers from the C library glfw)
typedef void (* GLFWmonitorfun)(GLFWmonitor* monitor, int event);
which is used like this:
void glfwSetMonitorCallback(GLFWmonitorfun cbfun)
I know I could wrap the function pointers in std::function
, but it is quite cumbersome, since there are about 30 different function pointer types in the library, and I would need to patch manually all their usages.
Is there an easier way ?
PS: Below is how I could wrap them with std::function. Beware, this is borderline atrocious, since it requires global variable and global callbacks. This is exactly what I would like to avoid.
// C function pointer type
typedef void (* GLFWmonitorfun)(GLFWmonitor* monitor, int event);
// Wrapper std::function type
using GLFWmonitorfun_std = std::function<void(GLFWmonitor*, int)>;
// We need to store a global std::function pointer
GLFWmonitorfun_std globalGLFWmonitorfun_std;
// As well as a global C callback
void myGlobalCallback(GLFWmonitor* monitor, int event)
{
if (globalGLFWmonitorfun_std)
globalGLFWmonitorfun_std(monitor, event);
}
void py_init_module_glfw(py::module& m)
{
m.def("glfwSetMonitorCallback", [](const GLFWmonitorfun_std& f) {
// And we set the global std::function pointer in the binding
globalGLFWmonitorfun_std = f;
// Before setting the C callback
glfwSetMonitorCallback(myGlobalCallback);
});
// ....
CodePudding user response:
Well, you need to store the function state somewhere. The simplest solution would be to put it in a local static
variable:
m.def("glfwSetMonitorCallback", [](std::function<std::remove_pointer_t<GLFWmonitorfun>> f) {
static std::function<std::remove_pointer_t<GLFWmonitorfun>> callback;
callback = std::move(f);
glfwSetMonitorCallback([](GLFWmonitor* monitor, int event) {
callback(monitor, event);
});
});
CodePudding user response:
A follow-up to @ecatmur excellent suggestion, it is possible to use macros to reduce the final code even more:
For example, below we can define two macros for callbacks that use one or two params:
#define ADD_FN_POINTER_CALLBACK_ONE_PARAM(functionName, CallbackType, Type1) \
m.def(#functionName, [](std::function<std::remove_pointer_t<CallbackType>> f) { \
static std::function<std::remove_pointer_t<CallbackType>> callback; \
callback = std::move(f); \
functionName([](Type1 v1) { \
callback(v1); \
});\
});
#define ADD_FN_POINTER_CALLBACK_TWO_PARAMS(functionName, CallbackType, Type1, Type2) \
m.def(#functionName, [](std::function<std::remove_pointer_t<CallbackType>> f) { \
static std::function<std::remove_pointer_t<CallbackType>> callback; \
callback = std::move(f); \
functionName([](Type1 v1, Type2 v2) { \
callback(v1, v2); \
});\
});
And we can use them like this:
ADD_FN_POINTER_CALLBACK_TWO_PARAMS(glfwSetMonitorCallback, GLFWmonitorfun, GLFWmonitor*, int);
Roast me!