4
\$\begingroup\$

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 <map>

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<void()> callback);
} // namespace Engine
#endif

GLContext.cpp:

#include <iostream>

#include <Engine/Types.hpp>
#include <Engine/GLContext.hpp>

namespace Engine
{

namespace { // Anonymous namespace to hide state from global scope
     std::map<GLContext::GLFWKeyCode, std::function<void()>> 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<void()> callback)
{
    key_handlers[key] = callback;
}
} // namespace Engine

The issue I have with this code is that ALL GLContexts share the same callbacks. I don't expect to have more than one GLContext at a time, but if I can somehow encapsulate it that would be ace. The other issue is that clang gives me warnings about exit-time destructor for the callback map, which I understand to be an issue if some of its data is freed before the end of the program.

To get rid of the compiler warning I could heap-allocate the map and handle the resources using RAII in the GLContext class:

GLContext-RAII.hpp:

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

#include <map>

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();
    void register_key_handler(GLContext::GLFWKeyCode key, std::function<void()> callback);
private:
    GLFWwindow* _window;
};

} // namespace Engine
#endif

GLContext-RAII.cpp:

#include <iostream>

#include <Engine/Types.hpp>
#include <Engine/GLContext.hpp>

namespace Engine
{
namespace { // Anonymous namespace to hide state from global scope

    typedef std::map<GLContext::GLFWKeyCode, std::function<void()>> KeyHandlers;
    KeyHandlers* key_handlers;

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

    // Allocate space for key_handlers on the heap;
    key_handlers = new KeyHandlers;

    glfwSetKeyCallback(_window,
        [] (GLFWwindow* window, int key, int scancode, int action, int mode) {
            if (action == GLFW_PRESS) {
                auto handler = key_handlers->find(key);
                if(handler != key_handlers->end())
                    handler->second();
            }
        });

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

    // .. More init code
}

// Rest of implementation

GLContext::~GLContext()
{
    // Deallocate key_handlers map
    delete key_handlers;
    glfwTerminate();
}

void GLContext::register_key_handler(GLContext::GLFWKeyCode key, std::function<void()> callback)
{
    key_handlers->insert({key, callback});
}
} //namespace Engine

This has removes the exit-time destructor warning, with the caveat that the code is uglier (to me at least), has explicit news and deletes (not idiomatic C++11/14) and even worse the key_handlers pointer will leak resources if more than one GLContext is created (rather than sharing like in the first example).

Is there an elegant way to get rid of the exit-time destructor warnings (sans -Wno-exit-time-destructors) and better yet allowing separate GLContexts to specify different handlers or should I just go and make GLContext a singleton and be done with it.

\$\endgroup\$
  • \$\begingroup\$ The callback returns a pointer to the window. See: GLFWkeyfun On the window you can store a user defined pointer glfwSetWindowUserPointer(). This is where you are supposed to store any application specific data for a callback (which can be an object with the real method calls you want to call. \$\endgroup\$ – Martin York Feb 9 '16 at 17:22
5
\$\begingroup\$

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<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();
}

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

\$\endgroup\$
  • \$\begingroup\$ Wow. Thank you for the amazingly fast response. That looks absolutely perfect for the issue I am having. "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" This bit of insight will go a long way as well. Thank you. \$\endgroup\$ – djrollins Feb 9 '16 at 10:43
  • \$\begingroup\$ @djrollins there are exceptions to that (coughglutcough) but most of them will. It's typically the first thing I look for whenever I see a function pointer callback. \$\endgroup\$ – ratchet freak Feb 9 '16 at 10:48

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.