Pointing to a function that is a class member - GLFW setKeycallback

26,914

Solution 1

There is a C++ syntax for pointing to class member methods but you cannot pass them to a C style API. C understands function calls and every non-static object method, taking your events as an example, looks like this thinking in C terms: void events(void* this, int, int); meaning that every method apart from the standard arguments also gets a this pointer silently passed.

To make your events C compatible make it static void events(int, int);. This way it will follow the C calling semantics - it will not require a this pointer getting passed. You have to also somehow pass your object to this callback in some other manner (if you need this object's data in the callback).

Solution 2

The code examples provided in the other answers don't describe how to redirect your callback to a per-object member function, with possibly any number of objects. Making your class a singleton will constrain your design and will not scale to multiple glfw windows.

The scalable solution is to set the glfw window user pointer to your object and then fetch it in the callback, and call the member function :

class MyGlWindow
{
public:
     void mouseButtonPressed();
};

void makeWindow()
{
    GLFWwindow* glfwWindow;
    MyGlWindow* myWindow;

    /* ... Initialize everything here ... */

    glfwSetWindowUserPointer(glfwWindow, myWindow);

    auto func = [](GLFWwindow* w, int, int, int)
    {
        static_cast<MyGlWindow*>(glfwGetWindowUserPointer(w))->mouseButtonPressed( /* ... */ );
    }

    glfwSetMouseButtonCallback(glfwWindow, func);
}

This solution is shorter and will work for any number of windows.

Solution 3

I also ran into this problem with another glfw callback function, but I didn't want to declare my class method as static, because I needed to access the member variables within. So I tried std::function and std::bind for giving me the ability to bind an instance method as the callback function, but unfortunately it's not an option when working with C callbacks.

The answer to this problem is also stated in the GLFW FAQ "How do I use C++ methods as callbacks":

You cannot use regular methods as callbacks, as GLFW is a C library and doesn’t know about objects and this pointers. If you wish to receive callbacks to a C++ object, use static methods or regular functions as callbacks, store the pointer to the object you wish to call in some location reachable from the callbacks and use it to call methods on your object.

However, this encouraged me to apply the Singleton pattern for my callback class and integrate it as following:

  • the callback method of my class is still static, so it can be specified/used as glfw callback
  • this static callback method makes use of the singleton and passes the callback parameters to an instance method
  • this instance method actually handles the callback parameters, with the benefit of being able to access the member variables

This is what it looks like:

// Input.h (the actual callback class for glfwSetMouseButtonCallback)
class Input 
{
public:
    static Input& getInstance() // Singleton is accessed via getInstance()
    {
        static Input instance; // lazy singleton, instantiated on first use
        return instance;
    }

    static void mouseButtonCallback(int key, int action) // this method is specified as glfw callback
    {
        //here we access the instance via the singleton pattern and forward the callback to the instance method
        getInstance().mouseButtonCallbackImpl(key, action); 
    }

    void mouseButtonCallbackImpl(int key, int action) //this is the actual implementation of the callback method
    {
        //the callback is handled in this instance method           
        //... [CODE here]
    }

private:
    Input(void) // private constructor necessary to allow only 1 instance
    {
    }

    Input(Input const&); // prevent copies
    void operator=(Input const&); // prevent assignments
};

and in my main.cpp:

Input &hexmap = Input::getInstance(); // initialize the singleton

//The glfw callback is set up as follows:   
glfwSetMouseButtonCallback( &Input::mouseButtonCallback); // specifying the static callback method, which internally forwards it to the instance method

Solution 4

I had the same problem and after reading this thread I came up with a similar solution. I think it is a bit cleaner this way. It's based on static function but it is nested inside the class where we set all things.

Header looks like this:

class Application
    {
    public:
        ...
    private:
        ...
        void MousePositionCallback(GLFWwindow* window, double positionX, double positionY);
        void KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
        ...
        class GLFWCallbackWrapper
        {
        public:
            GLFWCallbackWrapper() = delete;
            GLFWCallbackWrapper(const GLFWCallbackWrapper&) = delete;
            GLFWCallbackWrapper(GLFWCallbackWrapper&&) = delete;
            ~GLFWCallbackWrapper() = delete;

            static void MousePositionCallback(GLFWwindow* window, double positionX, double positionY);
            static void KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
            static void SetApplication(Application *application);
        private:
            static Application* s_application;
        };
    };

And the source code:

void Application::GLFWCallbackWrapper::MousePositionCallback(GLFWwindow* window, double positionX, double positionY)
{
    s_application->MousePositionCallback(window, positionX, positionY);
}

void Application::GLFWCallbackWrapper::KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    s_application->KeyboardCallback(window, key, scancode, action, mods);
}

void Application::GLFWCallbackWrapper::SetApplication(Application* application)
{
    GLFWCallbackWrapper::s_application = application;
}

Application* Application::GLFWCallbackWrapper::s_application = nullptr;

void Application::MousePositionCallback(GLFWwindow* window, double positionX, double positionY)
{
    ...
}

void Application::KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
    ...
}

void Application::SetCallbackFunctions()
{
    GLFWCallbackWrapper::SetApplication(this);
    glfwSetCursorPosCallback(m_window, GLFWCallbackWrapper::MousePositionCallback);
    glfwSetKeyCallback(m_window, GLFWCallbackWrapper::KeyboardCallback);
}

Solution 5

Inspired by N0vember's answer, I present you even more generic and dynamic solution:

class MyGlWindow {
public:
    std::function<void(MyGlWindow*)> onClose;
    std::function<void(MyGlWindow*, int, int, int)> onMouseClick = [](auto self, int, int, int) { /*some default behavior*/ };
};

void makeWindow() {
    GLFWwindow* glfwWindow;
    MyGlWindow* myWindow;

    /* ... Initialize everything here ... */

    glfwSetWindowUserPointer(glfwWindow, myWindow);

    #define genericCallback(functionName)\
        [](GLFWwindow* window, auto... args) {\
            auto pointer = static_cast<MyGlWindow*>(glfwGetWindowUserPointer(window));\
            if (pointer->functionName) pointer->functionName(pointer, args...);\
        }

    glfwSetWindowCloseCallback(glfwWindow, genericCallback(onClose));
    glfwSetMouseButtonCallback(glfwWindow, genericCallback(onMouseClick));

    myWindow->onMouseClick = [](auto self, int, int, int) {
        std::cout << "I'm such a rebel" << std::endl;
        self->onClose = [](auto self) {
            std::cout << "I'm such a rebellion" << std::endl;
        };
    };
}
Share:
26,914
viraj
Author by

viraj

Fork my projects on Github: https://github.com/virajmahesh

Updated on October 07, 2021

Comments

  • viraj
    viraj over 2 years

    I'm writing a GLFW app, in which I've wrapped the function calls into a simple class. I'm having trouble setting the key callback. My class is defined as:

    class GAME 
    {
    private:
        bool running;
    public:
        GAME();
        int execute();
        void events(int, int);
        int loop();
        int render();
    };
    

    The execute function is:

    int GAME::execute() 
        {
            glfwOpenWindow(640, 320, 8, 8, 8, 8, 0, 0, GLFW_WINDOW);
            glfwSetWindowTitle("Viraj");
            glfwSetKeyCallback(events);
            running = true;
            while(glfwGetWindowParam(GLFW_OPENED))
            {
                glfwPollEvents();
                loop();
                render();
            }
            return 0;
        }
    

    Compiling the following code on Visual Studio 2010 gives the error:

    error C3867: 'GAME::events': function call missing argument list; use '&GAME::events' to create a pointer to member

    Using &GAME::events gives:

    error C2664: 'glfwSetKeyCallback' : cannot convert parameter 1 from 'void (__thiscall GAME::* )(int,int)' to 'GLFWkeyfun' 1> There is no context in which this conversion is possible

  • Dustin Biser
    Dustin Biser almost 11 years
    Nice example, and also good coding practice to prevent copies and assignments for singleton.
  • Niklas
    Niklas over 9 years
    How would you notify the camera object that some things need to be changed? I don't want to make my Camera a Singleton.
  • Ben Voigt
    Ben Voigt about 9 years
    This is a particular case of "store the pointer to the object you wish to call in some location reachable from the callbacks" quoted in @Mobiletainment's answer. Definitely worth mentioning the window user pointer as a way to do that, but your first paragraph could use rewording.
  • N0vember
    N0vember about 9 years
    @BenVoigt Well it is present in Mobiletainment's answer but the emphasis and the phrasing are targeted toward a very different solution making it go unnoticed. A singleton is far from being a trivial design pattern. I rephrased to make it a bit clearer.
  • 8bitslime
    8bitslime about 9 years
    Could you provide an example of passing the object's data to the static method?
  • Nooble
    Nooble over 8 years
    A lazy singleton may result in a static initialization order fiasco.
  • Nooble
    Nooble over 8 years
    Also, as per C++11, you can use delete to delete the copy constructor and assignment operator.
  • gnzlbg
    gnzlbg almost 8 years
    Wow thanks! This is exactly what I needed to be able to pass arbitrary contexts to the GLFW callbacks! It feels really hacky though, I wonder why one can't just pass a context to poll events that then gets forwarded to the callbacks.
  • Admin
    Admin almost 8 years
    This is fine, but it follows the same logic as the simpler solution of making the callback a static function, and making the resources that the callback needs static class members. However, in this case, you are only allowing a single instance of the resources. For instance, this won't work if you have to handle multiple glfw windows. Worth mentioning this in the answer.
  • Dynamitos
    Dynamitos over 7 years
    GLFW has now a so-called "user-pointer", which is window-specific and can be accessed everywhere, which makes it possible to set this as a user pointer and call it in the callback.
  • Lightbulb1
    Lightbulb1 about 4 years
    This pointed me in the right direction. In a lot of c libraries you can supply a user data pointer when you call the function to set the callback. Then its passed as a parameter to the callback function. I couldn't believe GLFW didn't have a way of doing that. This is it!