How to glfwSetKeyCallback for different classes?

10,189

Solution 1

Things get much easier when you step back for a moment and don't think in classes at all. Also a class is a type and types don't really describe a specific state. I think what you actually mean are instances if a class with different internal state.

So your problem then becomes to associate a callback with your state. In your post you wrote the following incomplete callback signature:

keycallback(GLFWwindow *window, int key, int scancode, int action, int mods);

There is missing something, namely the type it returns. Which is void so the full signature is

void GLFWCALL keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);

Where GLFWCALL is a not further specified macro that expands into additional type signature qualifiers. You don't have to care about it anymore, than that this is the only type signature you can validly pass as a callback. Now image you'd write something like

class FooState
{
    void GLFWCALL keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods);  
};

What would be the type signature of this? Well, when you actually implement it, you're writing it down

void GLFWCALL FooState::keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);  

Look at this additional FooState::. This is not just some addition to the name or a namespace. It's actually a type modifier. It's like as if you added another parameter to the function (which is what in fact happens), and that parameter is a reference or a pointer (a pointer in the case of C++) to the class instance. This pointer is usually called this. So if you were to look at this function through the eyes of a C compiler (which doesn't know classes) and GLFW is written in C, the signature in fact looks like

void GLFWCALL keycallback(
    FooState *this,
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);  

And it's pretty obvious that this doesn't fit the needs of GLFW. So what you need is some kind of wrapper that gets this additional parameter there. And where do you get that additional parameter from? Well that would be another variable in your program you have to manage.

Now we can use static class members for this. Within the scope of a class static means a global variable shared by all instances of the class and functions that are within the namespace of the class, but don't work on instances of it (so from a C compiler's perspective they're just regular functions with no additional parameter).

First a base class that gives us a unified interface

// statebase.h
class StateBase
{
    virtual void keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods) = 0; /* purely abstract function */

    static StateBase *event_handling_instance;
    // technically setEventHandling should be finalized so that it doesn't
    // get overwritten by a descendant class.
    virtual void setEventHandling() { event_handling_instance = this; }

    static void GLFWCALL keycallback_dispatch(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods)
    {
        if(event_handling_instance)
            event_handling_instance->keycallback(window,key,scancode,action,mods);
    }    
};

// statebase.cpp
#include "statebase.h"
// funny thing that you have to omit `static` here. Learn about global scope
// type qualifiers to understand why.
StateBase * StateBase::event_handling_instance;

Now the purely abstract virtual function is the trick here: Whatever kind of class derived from StateBase is referenced by the event_handling_instance by use of a virtual dispatch it will end up being called with the function implementation of the class type of that instance.

So we can now declare FooState being derived from StateBase and implement the virtual callback function as usual (but omit that GLFWCALL macro)

class FooState : BaseState
{
    virtual void keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods);  
};
void FooState::keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods)
{
    /* ... */
}

To use this with GLFW register the static dispatcher as callback

glfwSetKeyCallback(window, StateBase::keycallback_dispatcher);

And to select a specific instance as active callback handler you call its inherited void StateBase::setEventHandling() function.

FooState state;
state.setEventHandling();

A few parting words of advice:

If you've not been familiar with these concepts of OOP in C++ and the intricate how types and instances interact you should not do any OOP programming, yet. OOP was once claimed to make things easier to understand, while in fact the shrewd ways it got implemented in C++ actually makes one brain hurt and proliferates very bad style.

Here's an exercise for you: Try to implement what you try to achieve without using OOP. Just use structs without member functions in them (so that they don't become classes in disguise), try to think up flat data structures and how to work with them. It's a really important skill which people who jump directly into OOP don't train a lot.

Solution 2

What you need for this to work is known as a closure (provides the necessary context to reference non-local storage). You cannot access class members in a traditional C callback because there is no way to satisfy the calling convention of a C++ class function (namely the added requirement of a this pointer).

Most frameworks get around this by allowing you to supply some void * user-data that is either passed directly to your callback or that can be queried when the callback is triggered. You can cast this void * to anything you want, in this case a pointer to a class object, and then invoke a member function belonging to that object.

By the way, GLFWCALL should really add nothing to this discussion. That is a macro that defines the default calling convention for the platform. It will never help you call a member function in a C++, its primary purpose was to ease things like DLL imports/exports.

Solution 3

What @andon-m-coleman said

    auto keyCallback = []( GLFWwindow* window, int key, int scancode, int action, int mods ) {
        auto me = (Window*)glfwGetWindowUserPointer( window );
        me->KeyCallback( window, key, scancode, action, mods );
    };
    glfwSetWindowUserPointer( window, this );
    glfwSetKeyCallback( window, keyCallback );
Share:
10,189
Dunkelbunt27
Author by

Dunkelbunt27

Updated on July 06, 2022

Comments

  • Dunkelbunt27
    Dunkelbunt27 almost 2 years

    I use GLFW and I have different classes representing the different states in my Application and one state-managing class. I want to use GLFW to receive the key-input and pass it to the current state (so to another class).

    The only way I could think of was giving each class an own keycallback-function and use glfwSetKeyCallback(window, keyCallback);

    keycallback(GLFWwindow *window, int key, int scancode, int action, int mods)
    

    It didn't work that way

    cannot convert 'IntroState::handleKeyEvent' from type 'void (IntroState::)(GLFWwindow*, int, int, int, int)' to type 'GLFWkeyfun {aka void (*)(GLFWwindow*, int, int, int, int)}'
         glfwSetKeyCallback(m_manager->window, handleKeyEvent);
    

    People recommended something like that:

    void GLFWCALL functionname( int key, int action );
    

    But the GLFWCALL-macro has been removed from GLFW (Official Note).


    I have read that you can use a static function.

    static void keyCallback(GLFWwindow*, int, int, int);
    

    But I need to access non-static member functions and get some problems when the callback-function is static.


    The actual question:

    How can I make the current "state"-class get the key-input from GLFW? like

    states.back()->handlekeyEvent(int, int, int);
    
  • Dunkelbunt27
    Dunkelbunt27 over 10 years
    Thanks for the replay, but I've never heard of this before and I actually don't get it. At which point should I use these void* casts? Maybe you can give an example for my case? That would help a lot
  • Andon M. Coleman
    Andon M. Coleman over 10 years
    What don't you get? You know that class objects have a this pointer, correct? C callback functions alone only define a single pointer, the address of the function. You have to augment this with the pointer to your class object. As for how this is done in GLFW, I could not say - I have never used the framework, but usually you can associate user data (void *) with some opaque type such as your window. Then when your callback is triggered, query the user data for your window and invoke the appropriate member function. If you need an example, I would suggest datenwolf's answer.
  • Dunkelbunt27
    Dunkelbunt27 over 10 years
    Thank you for the huge reply! I still have a few questions. I am not sure about this line StateBase * StateBase::event_handling_instance; I get some multiple definition errors.
  • datenwolf
    datenwolf over 10 years
    @Dunkelbunt27: I thought it would go without explicitly saying, that the definition would go into a .cpp file, just like member functions, while the declaration within the class definition goes into the .h file. C++ is a nasty language, because of pitfalls like that one. IMHO its not a very good language to learn OOP and its an even worse as a programming beginner's first language.
  • Dunkelbunt27
    Dunkelbunt27 over 10 years
    okay nevermind, I was just confused. It actually works pretty well so far. I will now try to implement other callbacks like mouse. Thanks again for the good explanation.
  • Stradigos
    Stradigos over 8 years
    @datenwolf This is all good and great, but how should I check for these key callbacks from inside a camera class's update function? I'm stuck between wanting to control the camera from FooState::keycallback() and wanting to subscribe to the state's input from inside the camera class. Both involve some sort of coupling though, and both feel wrong.
  • datenwolf
    datenwolf over 8 years
    @Stradigos: There's no silver bullet for your problem. In most games the standard solution involves accumulating all input events that happen between frames use an action mapping table to turn them into control variables for certain "actors"; these actors then consume those variables. This all of course strongly depends on the structuring of your code. If you have a "camera" class it obviously "lives" in some world, or is tied to some entity; which in turn lives in the world. So you could have some event dispatch that simply delivers events down the scene hierarchy as well.
  • Stradigos
    Stradigos over 8 years
    @datenwolf An action mapping being something like: "W" means move forward? Move forward then changes a variable that is somehow sent/observed by the actors? Camera is an entity, but I don't think delivering them down the entity scene hierarchy is good. There's a lot of objects I'd have to query. I'd rather do something from my camera object that check input state. Does this put my camera entity on the UI thread though?
  • datenwolf
    datenwolf over 8 years
    @Stradigos: "W move forward"?: Yes – " Does this put my camera entity on the UI thread though?": That's entirely up to you.
  • winduptoy
    winduptoy over 8 years
    "Non-static member function reference required." Solve it by making it static. i.giphy.com/EldfH1VJdbrwY.gif
  • stanleyerror
    stanleyerror about 8 years
    @datenwolf As your answer, should I assign StateBase * StateBase::event_handling_instance to the target instance if I need GLFW to invoke the callback of the target instance?
  • FrogTheFrog
    FrogTheFrog over 7 years
    While the answer is nice and neat for beginners, I just want to let people know that it is possible and fairly easy to make an automatic dispatcher (one that uses GLFWwindow *window itself instead StateBase* event_handling_instance)
  • Anton Duzenko
    Anton Duzenko almost 3 years
    Today I would not even call glfwSetWindowUserPointer. this can be captured by a lambda itself.