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

前端 未结 7 1151
遇见更好的自我
遇见更好的自我 2020-12-02 11:27

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

         


        
相关标签:
7条回答
  • 2020-12-02 11:52

    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;
            };
        };
    }
    
    0 讨论(0)
  • 2020-12-02 11:55

    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).

    0 讨论(0)
  • 2020-12-02 11:57

    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
    
    0 讨论(0)
  • 2020-12-02 12:00

    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.

    0 讨论(0)
  • 2020-12-02 12:13

    This is a useful discussion of possible solutions that helped me with the same problem, and I'm adding my solution in case it proves useful.

    Problem Statement

    My scenario is more general than the ones addressed by BIC, L.Senionis, and N0vember. In particular, my use case requires:

    • Generally, instance's data must be accessible to the callback
    • Many applications can be created using a common set of response handlers
    • In an application, any number of windows may be created
    • The set of callbacks attached to each window should be mixed and matched from a certain library of possible responders.

    Proposed Solution Usage

    The simple singleton design no longer solves the problem. Instead, I provide a GLFWResponder superclass that handles all of the setup complexity. In order to use the class and attach response to a window, here is what is required.

    // Implement custom responder
    class MyResponder : public GLFWResponder {
      public:
        virtual void cursor_position_callback(GLFWwindow* w, double x, double y) {...}
        ... override relevant callbacks ...
     };
    
    // in main ************************************************
    
    // Assuming initialized GLFWwindow* my_window and my_other_window
    
    MyResponder resp;
    MyResponder resp2;  // Can be another subclass of GLFWResponder
    
    // Two responders can respond to same window
    resp.respond_to(my_window, GLFWResponder::CURSOR_POSITION);
    resp2.respond_to(my_window, GLFWResponder::CURSOR_POSITION);
    
    // One responder can respond to multiple windows
    resp2.respond_to(my_other_window, GLFWResponder::CURSOR_POSITION);
    
    // One window can have different handlers for different events
    resp.respond_to(my_other_window, GLFWResponder::CURSOR_ENTER);
    

    Proposed Solution Implementation

    Here is the sketch of the GLFWResponder implementation, fully functional, but with some TODO's. There may be some implications on performance, which I have not yet investigated.

    // GLFWResponder.h ************************************************
    /**
     * Responder superclass that allows subclasses to handle events from multiple
     * GLFW windows (which have only C API for callbacks).
     * Callbacks are automatically cleaned up when responder goes out of scope.
     */
    class GLFWResponder {
     public:
      virtual ~GLFWResponder();
    
      // Interface -----------------------------------
      enum GLFWEventType {
        CURSOR_POSITION = 0,
        CURSOR_ENTER = 1
        // TODO: add support for other callbacks
      };
    
      void respond_to(GLFWwindow* window, GLFWEventType event);
    
      bool does_respond_to(GLFWwindow* window, GLFWEventType event) const;
    
      // Subclasses implement ------------------------
      virtual void cursor_position_callback(GLFWwindow* window, double xpos, double ypos);
    
      virtual void cursor_enter_callback(GLFWwindow* window, int entered);
    
      // TODO: add support for other callbacks
    
    
      // Under the hood ------------------------------
      static std::set<GLFWResponder*> getResponders(GLFWwindow* windo, GLFWEventType event);
    
     private:
      // Windows and events that this instance responds to
      std::set<std::pair<GLFWwindow*, GLFWEventType> > enabled_events_;
    
      // Global responders keyed by events they respond to
      // (each responder knows which windows it responds to)
      static std::map<GLFWEventType, std::set<GLFWResponder*> > responders_;
    }; 
    
    // GLFWResponder.cpp **************************************************
    namespace {
    
    void cursor_position_callback_private(GLFWwindow* window, double xpos, double ypos) {
      for (GLFWResponder* r : GLFWResponder::getResponders(window, GLFWResponder::CURSOR_POSITION)) {
        r->cursor_position_callback(window, xpos, ypos);
      }
    }
    
    void cursor_enter_callback_private(GLFWwindow* window, int entered) {
      for (GLFWResponder* r : GLFWResponder::getResponders(window, GLFWResponder::CURSOR_ENTER)) {
        r->cursor_enter_callback(window, entered);
      }
    }
    
    } // namespace
    
    std::map<GLFWResponder::GLFWEventType, std::set<GLFWResponder*> > GLFWResponder::responders_;
    
    GLFWResponder::~GLFWResponder() {
      for (auto& pr : responders_) {
        pr.second.erase(this);
      }
      // TODO: also clean up window's callbacks
    }
    
    void GLFWResponder::respond_to(GLFWwindow* window, GLFWResponder::GLFWEventType event) {
      enabled_events_.insert(std::make_pair(window, event));
      responders_[event].insert(this);
      if (event == CURSOR_POSITION) {
        glfwSetCursorPosCallback(window, cursor_position_callback_private);
      } else if (event == CURSOR_ENTER) {
        glfwSetCursorEnterCallback(window, cursor_enter_callback_private);
      } else {
        // TODO: add support for other callbacks
        LOG(FATAL) << "Unknown GLFWResponder event: " << event;
      }
    }
    
    bool GLFWResponder::does_respond_to(GLFWwindow* window, GLFWEventType event) const {
      return enabled_events_.find(std::make_pair(window, event)) != enabled_events_.end();
    }
    
    std::set<GLFWResponder*> GLFWResponder::getResponders(
        GLFWwindow* window, GLFWEventType event) {
      std::set<GLFWResponder*> result;
      auto it = responders_.find(event);
      if (it != responders_.end()) {
        for (GLFWResponder* resp : it->second) {
          if (resp->does_respond_to(window, event)) {
            result.insert(resp);
          }
        }
      }
      return result;
    }
    
    void GLFWResponder::cursor_position_callback(
        GLFWwindow* window, double xpos, double ypos) {
      // TODO: fail with message "GLFWResponder::do_respond called on a subclass that does not implement a handler for that event"
    }
    
    void GLFWResponder::cursor_enter_callback(GLFWwindow* window, int entered) {
      // TODO: fail with message "GLFWResponder::do_respond called on a subclass that does not implement a handler for that event"
    }
    
    0 讨论(0)
  • 2020-12-02 12:15

    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);
    }
    
    0 讨论(0)
提交回复
热议问题