Search code examples
c++glfw

Hide callback prototype from a 3rd party library in C++


I'm using glfw3 library, which has this function glfwSetCursorPosCallback, to use that function you need to pass a GLFWwindow pointer and a pointer to fuction with this prototype void callback(GLFWwindow* window, double, double).

I want to create a class WindowManager that encapsulate all operations with that library, so I save the GLFWindow as a variable member and I'm trying to hide the call to glfwSetCursorPosCallback in a void WindowManager::setCursorCallback(void (*callback)(double, double))

void WindowManager::aCursorPosCallback(GLFWwindow* window, double xpos, double ypos)
{
    this->cursorPosCallback(xpos, ypos);
}

void WindowManager::setCursorCallback(void (*callback)(double, double)) {
    this->cursorPosCallback = callback;
    glfwSetCursorPosCallback(this->window, this->aCursorPosCallback);
}

so the client of WindowManager does not have to keep a reference to the GLFwindow, something like this:

static void cursor_pos_callback(double xpos, double ypos)
{
   // HERE I have access to the values I care xpos and ypos
}

int main(int argc, char **argv) {
   WindowManager *wm = new WindowManager();
   wm->createWindow(WIDTH, HEIGHT)
   wm->setCursorCallback(cursor_pos_callback);
}

This code does not compile because I'm trying to pass a member function like it was a static function, but how can I achive this behavior if I can modify the 3rd party code?

I even try with labdas with no luck, the compiler says that lambdas can only be cast if they do not capture, so I'm stuck with it, any clue whould be helpful.

void WindowManager::setCursorCallback(void (*callback)(double, double)) {
    this->cursorPosCallback = callback;

    auto lamda =
    [callback](GLFWwindow* window, double xpos, double ypos)
    {
        printf("this works if no capture the callback");
        // callback(xpos, ypos);
    };

    glfwSetCursorPosCallback(this->window, (void(*)(GLFWwindow* window, double xpos, double ypos)) lamda);
}

Update:

base on @SparkyPotato comment, this should work and it does. Before set WindowManager as the user pointer with the window, glfwSetWindowUserPointer(this->window, this);

static void aCursorPosCallback(GLFWwindow* window, double xpos, double ypos)
{
    WindowManager *wm = (WindowManager *) glfwGetWindowUserPointer(window);
    wm->cursorPosCallback(xpos, ypos);
}

void WindowManager::setCursorCallback(void (*callback)(double, double)) {
    this->cursorPosCallback = callback;
    glfwSetCursorPosCallback(this->window, aCursorPosCallback);
}

Solution

  • GLFW allows you to set a window user pointer, which can be any void*. You can then retrieve the void*, and cast it to whatever type you wish. I think the functions as glfwSetWindowUserPointer() and glfwGetWindowUserPointer().

    What you can do is have the WindowManager register all the callbacks, and set the user pointer to this. Then, when the user wishes to set something, you can use an std::function in the WindowManager itself (so that they can pass lambdas or anything they wish), and then call that function from your GLFW callback by reinterpret_casting the window user pointer to a WindowManager.

    Example:

    class WindowManager
    {
    public:
      template<typename T>
      void SetCursorPosCallback(const T& userCallback)
      {
        m_UserCallback = userCallback;
      }
    
      void CreateWindow(/* args */)
      {
        GLFWwindow* window = glfwCreateWindow(/* args */);
        glfwSetWindowUserPointer(window, this);
        glfwSetCursorPosCallback(window, &WindowManager::CursorPosCallback);
      }
    
    private:
      static void CursorPosCallback(GLFWwindow* window, double x, double y)
      {
        WindowManager* manager = reinterpret_cast<WindowManager*>(glfwGetWindowUserPointer(window));
        manager.m_UserCallback(x, y);
      }
    
      std::function<void(double, double)> m_UserCallback;
    };