c++ - programacion - Apuntar a una función que es miembro de una clase-glfw setKeycallback
operador de resolucion de ambito c++ (7)
Estoy escribiendo una aplicación glfw, en la que he envuelto la función callse en una clase simple. Estoy teniendo problemas para configurar la devolución de llamada clave. Mi clase se define como:
class GAME
{
private:
bool running;
public:
GAME();
int execute();
void events(int, int);
int loop();
int render();
};
La función de ejecución es:
int GAME::execute()
{
glfwOpenWindow(640, 320, 8, 8, 8, 8, 0, 0, GLFW_WINDOW);
glfwSetWindowTitle("Viraj");
glfwSetKeyCallback(events);
running = true;
while(glfwGetWindowParam(GLFW_OPENED))
{
glfwPollEvents();
loop();
render();
}
return 0;
}
Al compilar el siguiente código en Visual Studio 2010, aparece el error:
error C3867: ''GAME::events'': function call missing argument list; use ''&GAME::events'' to create a pointer to member
Usando & GAME :: events da:
error C2664: ''glfwSetKeyCallback'' : cannot convert parameter 1 from ''void (__thiscall GAME::* )(int,int)'' to ''GLFWkeyfun'' 1> There is no context in which this conversion is possible
En el archivo de encabezado, realice los eventos (int, int) a un método estático. Eso solucionó el problema para mí.
class GAME
{
private:
bool running;
public:
GAME();
int execute();
static void events(int, int); //Changed here to static void
int loop();
int render();
};
Esta es una discusión útil sobre posibles soluciones que me ayudaron con el mismo problema, y estoy agregando mi solución en caso de que resulte útil.
Planteamiento del problema
Mi escenario es más general que los abordados por BIC, L.Senionis y N0vember. En particular, mi caso de uso requiere:
- Generalmente, los datos de la instancia deben ser accesibles a la devolución de llamada
- Se pueden crear muchas aplicaciones utilizando un conjunto común de controladores de respuesta
- En una aplicación, se puede crear cualquier número de ventanas
- El conjunto de devoluciones de llamada adjuntas a cada ventana debe mezclarse y combinarse con una biblioteca determinada de posibles respondedores.
Uso de la solución propuesta
El diseño simple de singleton ya no resuelve el problema. En su lugar, proporciono una superclase GLFWResponder
que maneja toda la complejidad de la configuración. Para usar la clase y adjuntar la respuesta a una ventana, esto es lo que se requiere.
// Implement custom responder
class MyResponder : public GLFWResponder {
public:
virtual void cursor_position_callback(GLFWwindow* w, double x, double y) {...}
... override relevant callbacks ...
};
// in main ************************************************
// Assuming initialized GLFWwindow* my_window and my_other_window
MyResponder resp;
MyResponder resp2; // Can be another subclass of GLFWResponder
// Two responders can respond to same window
resp.respond_to(my_window, GLFWResponder::CURSOR_POSITION);
resp2.respond_to(my_window, GLFWResponder::CURSOR_POSITION);
// One responder can respond to multiple windows
resp2.respond_to(my_other_window, GLFWResponder::CURSOR_POSITION);
// One window can have different handlers for different events
resp.respond_to(my_other_window, GLFWResponder::CURSOR_ENTER);
Implementación de la solución propuesta
Aquí está el bosquejo de la implementación de GLFWResponder
, completamente funcional, pero con algunos TODO. Puede haber algunas implicaciones en el rendimiento, que aún no he investigado.
// GLFWResponder.h ************************************************
/**
* Responder superclass that allows subclasses to handle events from multiple
* GLFW windows (which have only C API for callbacks).
* Callbacks are automatically cleaned up when responder goes out of scope.
*/
class GLFWResponder {
public:
virtual ~GLFWResponder();
// Interface -----------------------------------
enum GLFWEventType {
CURSOR_POSITION = 0,
CURSOR_ENTER = 1
// TODO: add support for other callbacks
};
void respond_to(GLFWwindow* window, GLFWEventType event);
bool does_respond_to(GLFWwindow* window, GLFWEventType event) const;
// Subclasses implement ------------------------
virtual void cursor_position_callback(GLFWwindow* window, double xpos, double ypos);
virtual void cursor_enter_callback(GLFWwindow* window, int entered);
// TODO: add support for other callbacks
// Under the hood ------------------------------
static std::set<GLFWResponder*> getResponders(GLFWwindow* windo, GLFWEventType event);
private:
// Windows and events that this instance responds to
std::set<std::pair<GLFWwindow*, GLFWEventType> > enabled_events_;
// Global responders keyed by events they respond to
// (each responder knows which windows it responds to)
static std::map<GLFWEventType, std::set<GLFWResponder*> > responders_;
};
// GLFWResponder.cpp **************************************************
namespace {
void cursor_position_callback_private(GLFWwindow* window, double xpos, double ypos) {
for (GLFWResponder* r : GLFWResponder::getResponders(window, GLFWResponder::CURSOR_POSITION)) {
r->cursor_position_callback(window, xpos, ypos);
}
}
void cursor_enter_callback_private(GLFWwindow* window, int entered) {
for (GLFWResponder* r : GLFWResponder::getResponders(window, GLFWResponder::CURSOR_ENTER)) {
r->cursor_enter_callback(window, entered);
}
}
} // namespace
std::map<GLFWResponder::GLFWEventType, std::set<GLFWResponder*> > GLFWResponder::responders_;
GLFWResponder::~GLFWResponder() {
for (auto& pr : responders_) {
pr.second.erase(this);
}
// TODO: also clean up window''s callbacks
}
void GLFWResponder::respond_to(GLFWwindow* window, GLFWResponder::GLFWEventType event) {
enabled_events_.insert(std::make_pair(window, event));
responders_[event].insert(this);
if (event == CURSOR_POSITION) {
glfwSetCursorPosCallback(window, cursor_position_callback_private);
} else if (event == CURSOR_ENTER) {
glfwSetCursorEnterCallback(window, cursor_enter_callback_private);
} else {
// TODO: add support for other callbacks
LOG(FATAL) << "Unknown GLFWResponder event: " << event;
}
}
bool GLFWResponder::does_respond_to(GLFWwindow* window, GLFWEventType event) const {
return enabled_events_.find(std::make_pair(window, event)) != enabled_events_.end();
}
std::set<GLFWResponder*> GLFWResponder::getResponders(
GLFWwindow* window, GLFWEventType event) {
std::set<GLFWResponder*> result;
auto it = responders_.find(event);
if (it != responders_.end()) {
for (GLFWResponder* resp : it->second) {
if (resp->does_respond_to(window, event)) {
result.insert(resp);
}
}
}
return result;
}
void GLFWResponder::cursor_position_callback(
GLFWwindow* window, double xpos, double ypos) {
// TODO: fail with message "GLFWResponder::do_respond called on a subclass that does not implement a handler for that event"
}
void GLFWResponder::cursor_enter_callback(GLFWwindow* window, int entered) {
// TODO: fail with message "GLFWResponder::do_respond called on a subclass that does not implement a handler for that event"
}
Hay una sintaxis de C ++ para señalar los métodos de los miembros de la clase, pero no puede pasarlos a una API de estilo C. C entiende las llamadas de función y todos los métodos de objetos no estáticos, tomando como ejemplo sus events
, tiene este aspecto en términos de C: void events(void* this, int, int);
lo que significa que cada método, aparte de los argumentos estándar, también obtiene un puntero que se pasa de forma silenciosa.
Para hacer que sus events
compatibles, static void events(int, int);
. De esta manera seguirá la semántica de llamada C, no requerirá que se pase this
puntero. También debe pasar de alguna manera su objeto a esta devolución de llamada de alguna otra manera (si necesita los datos de este objeto en la devolución de llamada).
Inspirado por la respuesta de noviembre, le presento una solución aún más genérica y dinámica:
class MyGlWindow {
public:
std::function<void(MyGlWindow*)> onClose;
std::function<void(MyGlWindow*, int, int, int)> onMouseClick = [](auto self, int, int, int) { /*some default behavior*/ };
};
void makeWindow() {
GLFWwindow* glfwWindow;
MyGlWindow* myWindow;
/* ... Initialize everything here ... */
glfwSetWindowUserPointer(glfwWindow, myWindow);
#define genericCallback(functionName)/
[](GLFWwindow* window, auto... args) {/
auto pointer = static_cast<MyGlWindow*>(glfwGetWindowUserPointer(window));/
if (pointer->functionName) pointer->functionName(pointer, args...);/
}
glfwSetWindowCloseCallback(glfwWindow, genericCallback(onClose));
glfwSetMouseButtonCallback(glfwWindow, genericCallback(onMouseClick));
myWindow->onMouseClick = [](auto self, int, int, int) {
std::cout << "I''m such a rebel" << std::endl;
self->onClose = [](auto self) {
std::cout << "I''m such a rebellion" << std::endl;
};
};
}
Los ejemplos de código proporcionados en las otras respuestas no describen cómo redirigir su devolución de llamada a una función miembro por objeto, con posiblemente cualquier número de objetos. Hacer que su clase sea un singleton limitará su diseño y no se escalará a múltiples ventanas de Glfw.
La solución escalable es establecer el puntero de usuario de la ventana glfw a su objeto y luego recuperarlo en la devolución de llamada, y llamar a la función miembro:
class MyGlWindow
{
public:
void mouseButtonPressed();
};
void makeWindow()
{
GLFWwindow* glfwWindow;
MyGlWindow* myWindow;
/* ... Initialize everything here ... */
glfwSetWindowUserPointer(glfwWindow, myWindow);
auto func = [](GLFWwindow* w, int, int, int)
{
static_cast<MyGlWindow*>(glfwGetWindowUserPointer(w))->mouseButtonPressed( /* ... */ );
}
glfwSetMouseButtonCallback(glfwWindow, func);
}
Esta solución es más corta y funcionará para cualquier número de ventanas.
También me encontré con este problema con otra función de devolución de llamada glfw, pero no quería declarar mi método de clase como static
, porque necesitaba acceder a las variables miembro dentro. Así que probé std::function
y std::bind
para darme la posibilidad de enlazar un método de instancia como la función de devolución de llamada, pero desafortunadamente no es una opción cuando se trabaja con devoluciones de llamada en C.
La respuesta a este problema también se encuentra en las preguntas frecuentes de GLFW "¿Cómo uso los métodos de C ++ como devoluciones de llamada?"
No puede usar métodos regulares como devoluciones de llamada, ya que GLFW es una biblioteca C y no conoce los objetos y estos punteros. Si desea recibir devoluciones de llamada a un objeto C ++, utilice métodos estáticos o funciones regulares como devoluciones de llamada , almacene el puntero al objeto al que desea llamar en alguna ubicación accesible desde las devoluciones de llamada y utilícelo para llamar métodos en su objeto.
Sin embargo, esto me animó a aplicar el patrón Singleton para mi clase de devolución de llamada e integrarlo de la siguiente manera:
- el método de devolución de llamada de mi clase sigue siendo estático, por lo que puede especificarse / usarse como devolución de llamada glfw
- este método de devolución de llamada estática utiliza el singleton y pasa los parámetros de devolución de llamada a un método de instancia
- este método de instancia realmente maneja los parámetros de devolución de llamada, con la ventaja de poder acceder a las variables miembro
Esto es lo que parece:
// Input.h (the actual callback class for glfwSetMouseButtonCallback)
class Input
{
public:
static Input& getInstance() // Singleton is accessed via getInstance()
{
static Input instance; // lazy singleton, instantiated on first use
return instance;
}
static void mouseButtonCallback(int key, int action) // this method is specified as glfw callback
{
//here we access the instance via the singleton pattern and forward the callback to the instance method
getInstance().mouseButtonCallbackImpl(key, action);
}
void mouseButtonCallbackImpl(int key, int action) //this is the actual implementation of the callback method
{
//the callback is handled in this instance method
//... [CODE here]
}
private:
Input(void) // private constructor necessary to allow only 1 instance
{
}
Input(Input const&); // prevent copies
void operator=(Input const&); // prevent assignments
};
y en mi main.cpp:
Input &hexmap = Input::getInstance(); // initialize the singleton
//The glfw callback is set up as follows:
glfwSetMouseButtonCallback( &Input::mouseButtonCallback); // specifying the static callback method, which internally forwards it to the instance method
Tuve el mismo problema y, después de leer este hilo, se me ocurrió una solución similar. Creo que está un poco más limpio de esta manera. Se basa en una función estática pero está anidada dentro de la clase donde establecemos todas las cosas.
El encabezado se ve así:
class Application
{
public:
...
private:
...
void MousePositionCallback(GLFWwindow* window, double positionX, double positionY);
void KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
...
class GLFWCallbackWrapper
{
public:
GLFWCallbackWrapper() = delete;
GLFWCallbackWrapper(const GLFWCallbackWrapper&) = delete;
GLFWCallbackWrapper(GLFWCallbackWrapper&&) = delete;
~GLFWCallbackWrapper() = delete;
static void MousePositionCallback(GLFWwindow* window, double positionX, double positionY);
static void KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
static void SetApplication(Application *application);
private:
static Application* s_application;
};
};
Y el código fuente:
void Application::GLFWCallbackWrapper::MousePositionCallback(GLFWwindow* window, double positionX, double positionY)
{
s_application->MousePositionCallback(window, positionX, positionY);
}
void Application::GLFWCallbackWrapper::KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
s_application->KeyboardCallback(window, key, scancode, action, mods);
}
void Application::GLFWCallbackWrapper::SetApplication(Application* application)
{
GLFWCallbackWrapper::s_application = application;
}
Application* Application::GLFWCallbackWrapper::s_application = nullptr;
void Application::MousePositionCallback(GLFWwindow* window, double positionX, double positionY)
{
...
}
void Application::KeyboardCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
{
...
}
void Application::SetCallbackFunctions()
{
GLFWCallbackWrapper::SetApplication(this);
glfwSetCursorPosCallback(m_window, GLFWCallbackWrapper::MousePositionCallback);
glfwSetKeyCallback(m_window, GLFWCallbackWrapper::KeyboardCallback);
}