1

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);
    });

    // ....

2 Answers 2

2

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);
    });
});
Sign up to request clarification or add additional context in comments.

5 Comments

The force is strong with you!
I got an issue with both this approach and the one with the global function - the python program runs correctly and never exits. I guess it can't clean up and hangs. I tried making a _cleanup function and manually calling the destructor (after moving the static one scope higher) but that just led to a segfault. Any ideas?
@Nickolai you don't need to manually call the destructor, just clear the std::function by assigning it the empty state: callback = nullptr;. Make sure that your GLFW monitor callback won't try to call it, resulting in bad_function_call - you could call glfwSetMonitorCallback(nullptr) at cleanup time, perhaps.
Thanks @ecatmur. I ended up setting callback to nullptr right after the function call, so I could keep everything scoped locally. That worked for me since I know the callback won't be called after the function returns, but might not work for everyone. I also found that if the Python function threw an exception, I'd get the same hanging issue, so I had to do the following within the function provided to glfsSetMonitorCallback: try { callback(monitor, event); } catch(...) {callback = nullptr; throw;}
Second update, I found that if I made the argument type in the lambda a py::function (or a py::object, which was necessary if I wanted the default argument value to be None) then I didn't need to clear the static variable holding onto the function, and I got no hangs.
0

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!

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.