0

I'm trying to register a class member function as a (regular) callback function. Unless I've misunderstood something, this should be possible using std::bind (using C++11). I do this the following way:

std::function<void (GLFWwindow*, unsigned int)> cb = std::bind(&InputManager::charInputCallback, this, std::placeholders::_1, std::placeholders::_2);

My callback function is defined in the following way:

void InputManager::charInputCallback(GLFWwindow* window, unsigned int key)

I'm able to test cb immediately after creating using random data:

cb(NULL, 0x62);

I can confirm that this data is sent correctly to the callback function by printing to the terminal from within it.

However, I want to register this function to GLFW so that the keypresses of the program window gets sent to the callback function. I do that like this:

glfwSetCharCallback(window, (GLFWcharfun) &cb);

Like I said before: calling it manually works just fine. When I register it as a callback though, I get a segmentation fault whenever I press a key and GLFW tries to call the callback function.

Is std::bind not what I'm looking for? Am I using it incorrectly?

Edit: I don't think this question is a duplicate of How can I pass a class member function as a callback? like it has been identified as. While we're adressing the same problem, I'm asking about this particular solution, using std::bind, which is only mentioned but never explained in one of the answers to the other question.

Sven G
  • 21
  • 7
  • Possible duplicate of [How can I pass a class member function as a callback?](http://stackoverflow.com/questions/400257/how-can-i-pass-a-class-member-function-as-a-callback) – Ari0nhh Sep 12 '16 at 10:14
  • C++ doesn't have garbage collection. `this` doesn't magically stay around just because you've bound to to a callback. If you didn't manage the lifetime correctly, your `InputManager` might have been destructed by the time GLFW calls the callback. This would cause a SegFault. However, SegFaults can have other causes too - hard to say without a minimal example. – MSalters Sep 12 '16 at 10:25
  • @MSalters: Thanks for pointing that out, but my object exists for the duration of the entire application. – Sven G Sep 12 '16 at 10:33

1 Answers1

1

Here is the function declaration:

GLFWcharfun glfwSetCharCallback (   GLFWwindow *    window,
                                    GLFWcharfun     cbfun 
                                )

Where GLFWcharfun is defined as typedef void(* GLFWcharfun) (GLFWwindow *, unsigned int)

There is an obvious problem here in that you do not get the opportunity to pass in a 'context' object which will automatically map the callback back to an instance of InputManager. So you will have to perform the mapping manually using the only key you have available - the window pointer.

Here is one strategy...

#include <map>
#include <mutex>

struct GLFWwindow {};
typedef void(* GLFWcharfun) (GLFWwindow *, unsigned int);

GLFWcharfun glfwSetCharCallback (   GLFWwindow *    window,
                                 GLFWcharfun    cbfun
                                 );


struct InputManager;

struct WindowToInputManager
{
    struct impl
    {
        void associate(GLFWwindow* window, InputManager* manager)
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            mapping_[window] = manager;
        }

        void disassociate(GLFWwindow* window, InputManager* manager)
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            mapping_.erase(window);
        }

        InputManager* find(GLFWwindow* window) const
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            auto i = mapping_.find(window);
            if (i == mapping_.end())
                return nullptr;
            else
                return i->second;
        }

        mutable std::mutex mutex_;
        std::map<GLFWwindow*, InputManager*> mapping_;
    };

    static impl& get_impl() {
        static impl i {};
        return i;
    }

    void associate(GLFWwindow* window, InputManager* manager)
    {
        get_impl().associate(window, manager);
    }

    void disassociate(GLFWwindow* window, InputManager* manager)
    {
        get_impl().disassociate(window, manager);
    }

    InputManager* find(GLFWwindow* window)
    {
        return get_impl().find(window);
    }

};

struct InputManager
{
    void init()
    {
        // how to set up the callback?

        // first, associate the window with this input manager
        callback_mapper_.associate(window_, this);

        // now use a proxy as the callback
        glfwSetCharCallback(window_, &InputManager::handleCharCallback);

    }

    static void handleCharCallback(GLFWwindow *     window,
                           unsigned int ch)
    {
        // proxy locates the handler
        if(auto self = callback_mapper_.find(window))
        {
            self->charInputCallback(window, ch);
        }

    }

    void charInputCallback(GLFWwindow *     window,
                           int ch)
    {
        // do something here
    }


    GLFWwindow* window_;
    static WindowToInputManager callback_mapper_;    
};

Or if you prefer closures:

#include <map>
#include <mutex>

struct GLFWwindow {};
typedef void(* GLFWcharfun) (GLFWwindow *, unsigned int);

GLFWcharfun glfwSetCharCallback (   GLFWwindow *    window,
                                 GLFWcharfun    cbfun
                                 );


struct InputManager;

struct WindowToInputManager
{
    using sig_type = void (GLFWwindow *, unsigned int);
    using func_type = std::function<sig_type>;

    struct impl
    {
        void associate(GLFWwindow* window, func_type func)
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            mapping_[window] = std::move(func);
        }

        void disassociate(GLFWwindow* window)
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            mapping_.erase(window);
        }

        const func_type* find(GLFWwindow* window) const
        {
            auto lock = std::unique_lock<std::mutex>(mutex_);
            auto i = mapping_.find(window);
            if (i == mapping_.end())
                return nullptr;
            else
                return std::addressof(i->second);
        }

        mutable std::mutex mutex_;
        std::map<GLFWwindow*, func_type> mapping_;
    };

    static impl& get_impl() {
        static impl i {};
        return i;
    }

    template<class F>
    void associate(GLFWwindow* window, F&& f)
    {
        get_impl().associate(window, std::forward<F>(f));
        glfwSetCharCallback(window, &WindowToInputManager::handleCharCallback);
    }

    void disassociate(GLFWwindow* window)
    {
        // call whatever is the reverse of glfwSetCharCallback here
        //

        // then remove from the map
        get_impl().disassociate(window);
    }

    const func_type* find(GLFWwindow* window)
    {
        return get_impl().find(window);
    }

    static void handleCharCallback(GLFWwindow* w, unsigned int ch)
    {
        auto f = get_impl().find(w);
        // note - possible race here if handler calls disasociate. better to return a copy of the function?
        if (f) {
            (*f)(w, ch);
        }
    }

};

struct InputManager
{
    void init()
    {
        callback_mapper_.associate(window_, [this](auto* window, int ch) { this->charInputCallback(window, ch); });

    }

    void charInputCallback(GLFWwindow * window,
                           int ch)
    {
        // do something here
    }


    GLFWwindow* window_;
    WindowToInputManager callback_mapper_;

};
Richard Hodges
  • 68,278
  • 7
  • 90
  • 142
  • I see how that might work. Of course, I'll have to access the window parameter using `this`, due to how the parameters are pushed on to the stack, but I definitively like the idea of a map. I'll give it a go before I mark this answer as accepted. After all, I was hoping for a std::bind solution. – Sven G Sep 12 '16 at 10:46
  • @SvenG the second solution is a "bind" solution (via the unavoidable map). But I have used a lambda instead of bind, because std::bind is an evil anachronism that was necessary before we had lambdas. It should be avoided today. – Richard Hodges Sep 12 '16 at 10:50
  • @SvenG I've just noticed that all glew functions must be called from the main thread, and callbacks will also arrive on that thread. In this case, there is probably no need for the mutex. – Richard Hodges Sep 12 '16 at 11:15