HiveBrain v1.2.0
Get Started
← Back to all entries
patterncppMinor

(C++14) Handling state in C-style function pointer callbacks

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
handlingfunctionpointerstylestatecallbacks

Problem

I'm having some issues coming up with a sensible solution for using shared state in a C-style function pointer. I am using GLFW to handle my OpenGL context and have created a thin wrapper around all of the setup code. I want to provide a way of registering callbacks for key presses, however the glfwSetKeyCallback takes a standard C-style function pointer so I cannot pass it a lambda that captures state from the Wrapper class.

Therefore, I currently stores the state in an anonymous namespace in the compilation unit and reference it in the GLContext class and handler registration code.

GLContext.hpp:

#ifndef ENGINE_GLCONTEXT_H
#define ENGINE_GLCONTEXT_H
#include "Types.hpp"
#include "OpenGL.hpp"

#include 

namespace Engine
{
class GLContext
{
public:
    typedef int32_t GLFWKeyCode;
    GLContext(int32_t width, int32_t height, const char* name);
    ~GLContext();
    bool running();
    void swap_buffers();
private:
    GLFWwindow* _window;
};

void register_key_handler(GLContext::GLFWKeyCode key, std::function callback);
} // namespace Engine
#endif


GLContext.cpp:

```
#include

#include
#include

namespace Engine
{

namespace { // Anonymous namespace to hide state from global scope
std::map> key_handlers;
}

GLContext::GLContext(int32_t width, int32_t height, const char* name)
{
// .. Init code

// Cannot use capturing lambda (for [this]) where a function pointer is
// expected so use the key_handlers map in anonymous namespace.
glfwSetKeyCallback(_window,
[] (GLFWwindow* window, int key, int scancode, int action, int mode) {
if (action == GLFW_PRESS && key_handlers[key])
key_handlers[key]();
});

// Tap escape key to close
register_key_handler(GLFW_KEY_ESCAPE, [this] () {
glfwSetWindowShouldClose(this->_window, GL_TRUE);
});

// .. More init code
}

// .. rest of implementation

void register_key_handler(GLContext::GLFWKeyCode key, std::function call

Solution

Whenever I see a callback in an API there is a high chance that there is a way to pass at least a custom void through to that callback. In glfw that's embedded in GLFWwindow which has a void user specified data in glfwGetWindowUserPointer and glfwSetWindowUserPointer.

You can use this to keep a pointer to the state per window and reinterpret_cast it to a struct pointer of your choice. In your case this will probably be the GLContext class.

GLContext::GLContext(int32_t width, int32_t height, const char* name):
   key_handlers()
{
    // key_handlers is now a member;

    // .. Init code

    glfwSetWindowUserPointer(_window, std::reinterpret_cast(this));

    glfwSetKeyCallback(_window,
        [] (GLFWwindow* window, int key, int scancode, int action, int mode) {
            auto thiz = std::reinterpret_cast(glfwGetWindowUserPointer(window));
            if (action == GLFW_PRESS) {
                thiz->handlePress(key);
            }
        });

    // Tap escape key to close
    register_key_handler(GLFW_KEY_ESCAPE, [this] () {
            glfwSetWindowShouldClose(this->_window, GL_TRUE);
        });

    // .. More init code
}

GLContext::handlePress(int key) {
    auto handler = key_handlers->find(key);
    if(handler != key_handlers->end())
        handler->second();
}


This approach can be taken for all callbacks. Keep in mind that they all have to use the same UserPointer.

Code Snippets

GLContext::GLContext(int32_t width, int32_t height, const char* name):
   key_handlers()
{
    // key_handlers is now a member;

    // .. Init code

    glfwSetWindowUserPointer(_window, std::reinterpret_cast<void*>(this));


    glfwSetKeyCallback(_window,
        [] (GLFWwindow* window, int key, int scancode, int action, int mode) {
            auto thiz = std::reinterpret_cast<GLContext*>(glfwGetWindowUserPointer(window));
            if (action == GLFW_PRESS) {
                thiz->handlePress(key);
            }
        });

    // Tap escape key to close
    register_key_handler(GLFW_KEY_ESCAPE, [this] () {
            glfwSetWindowShouldClose(this->_window, GL_TRUE);
        });

    // .. More init code
}

GLContext::handlePress(int key) {
    auto handler = key_handlers->find(key);
    if(handler != key_handlers->end())
        handler->second();
}

Context

StackExchange Code Review Q#119372, answer score: 5

Revisions (0)

No revisions yet.