patterncppMinor
(C++14) Handling state in C-style function pointer callbacks
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:
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
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
#endifGLContext.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
You can use this to keep a pointer to the state per window and
This approach can be taken for all callbacks. Keep in mind that they all have to use the same UserPointer.
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.