Give static function access to data without passing the data as a parameter
Asked Answered
S

2

5

I am using GLFW for a window in my C++ application and I am trying to get input events using GLFW's callbacks. For instance, this is how you get key events:

void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods){
    // Do something with event data.
}

int main(){
    // initialize window (I have no problems with this step)

    glfwSetKeyCallback(window, key_callback);
    // Now when a key is pressed in the window it will call this function.
}

The Problem:

In my key_callback I want to use a variable that is declared outside of the key_callback function and since I cannot alter the parameters of key_callback I cannot pass a reference to the variable.

Now, in the given example above, I could simply declare my variable outside of int main() and both key_callback and int main() would have access to the same instance of the variable.

My Desired Use:

I want to have a WindowWrapper class that creates and manages the life cycle of the glfwWindow, this would include setting the event callbacks.

WindowWrapper.h

// Includes
class WindowWrapper{
private:
    Centrum* g_centrum_;
    GLFWwindow* window_;
    std::thread thread_;
public:
    WindowWrapper();
    WindowWrapper(Centrum* g_centrum);
    ~WindowWrapper();
private:
    // Callbacks
    static void key_callback(
        GLFWwindow* window, int key, int scancode, int action, int mods
        );
};

WindowWrapper.cpp

WindowWrapper::WindowWrapper(Centrum* g_centrum){
    g_centrum_ = g_centrum;
    // Initialize window

    glfwSetKeyCallback(window_, key_callback); // Problems

    // Window loop and OpenGL stuff
}
WindowWrapper::~WindowWrapper(){
    thread_.join(); // Don't worry about this, it works but, I will make it safer.
    glfwDestroyWindow(window_);
    printf("WindowWrapper Completely Destructed!\n"); // For testing purposes
}

void WindowWrapper::key_callback(
    GLFWwindow* window, int key, int scancode, int action, int mods
    ){
    // This function is declared static in the class declaration.
    // And as a result I cannot use g_centrum_ since it is a non-static variable
    // Essentially I want to be able to access g_centrum_ from this function
    g_centrum_->input_eventmanager_->key_eventmanager_->
        TriggerKeyEvent(key, action, mods);
}

The first way I thought to do this was to pass a reference to g_centrum, but GLFW will not all any deviation from the parameters for a callback.

My second attempt was to declare and define the callbacks in the constructor, but you cannot do that.

My third attempt was to make g_centrum_ static, but I would have to give it the reference outside of the constructor and I don't think that to be an elegant solution.

Spleeny answered 22/12, 2014 at 5:7 Comment(6)
Rather than having WindowWrapper hold a GLFWwindow* pointer, derive it from GLFWwindow. Then key_callback can just cast its window parameter down to WindowWrapper* and access its members.Mackenzie
GLFW is a framework, and GLFWwindow is opaque.Damara
@Damara It's some struct. You don't need to know what's inside it in order to derive from it. If that feels wrong, another approach is to define a struct that has GLFWwindow as its first member (and then extra members afterwards). Again, one can then cast GLFWwindow* pointer to that struct pointer, and access extra members.Mackenzie
@IgorTandetnik: You cannot inherit from an opaque object.Damara
What's this "opaque object" you speak of? My copy of the C++ standard doesn't mention such a thing.Mackenzie
@IgorTandetnik: Opaque object is the colloquial term used for an incomplete type.Damara
D
9

Use glfwSetWindowUserPointer() to associate your wrapper pointer to the window before you register your callback. When your callback is called, you can use glfwGetWindowUserPointer() to retrieve it. These APIs are described in the GLFW documentation.

Window user pointer

Each window has a user pointer that can be set with glfwSetWindowUserPointer and fetched with glfwGetWindowUserPointer. This can be used for any purpose you need and will not modified by GLFW throughout the life-time of the window.

As an example, you could do this in your WindowWrapper constructor:

WindowWrapper::WindowWrapper(Centrum* g_centrum){
    g_centrum_ = g_centrum;
    // Initialize window first
    ...
    // Now, associate the wrapper to the window
    glfwSetWindowUserPointer(window_, this);

    glfwSetKeyCallback(window_, key_callback); // Problems

    // Window loop and OpenGL stuff
}

Then, in your callback:

void WindowWrapper::key_callback(
    GLFWwindow* window, int key, int scancode, int action, int mods
    ){

    void *data = glfwGetWindowUserPointer(window);  
    WindowWrapper *w = static_cast<WindowWrapper *>(data);

    w->g_centrum_->input_eventmanager_->key_eventmanager_->
        TriggerKeyEvent(key, action, mods);
}
Damara answered 22/12, 2014 at 5:31 Comment(1)
I am a little lost and the API page doesn't really give much for me to go on (with my little understanding that is). Could you please give an abstract example?Spleeny
T
2

You could have a static map that maps each GLFWindow* to its corresponding WindowWrapper:

private:
    static std::map<GLFWindow*, WindowWrapper*> m_instanceMap;
    ...

Then in the constructor, you add an entry to this map:

m_instanceMap.insert(std::make_pair(window_, this));

In the destructor, remove it:

m_instanceMap.erase(window_);

Now, in the (static) callback method, you can look up the class instance, and invoke a per instance method:

void WindowWrapper::key_callback(
    GLFWwindow* window, int key, int scancode, int action, int mods)
{
    WindowWrapper* pThis = m_instanceMap[window];
    pThis->keyHandler(key, scancode, action, mods);
}

Where keyHandler() is a regular instance method.

Tom answered 22/12, 2014 at 5:32 Comment(2)
A mapping is the only way to go if the framework does not provide support to associate user data (+1).Damara
Yes, this was intended as a generic solution, under the premise that there really isn't any support. Since it's apparently supported in this specific case, it's certainly better to use it.Tom

© 2022 - 2024 — McMap. All rights reserved.