Home > Mobile >  pybind11: pass *C-style* function pointer as a parameter
pybind11: pass *C-style* function pointer as a parameter

Time:11-18

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!

  • Related