Указывая на функцию, которая является членом класса-glfw setKeycallback
Я пишу приложение glfw, в котором я обернул функцию callse в простой класс. У меня возникли проблемы с настройкой обратного вызова ключа. Мой класс определяется как:
class GAME
{
private:
bool running;
public:
GAME();
int execute();
void events(int, int);
int loop();
int render();
};
функция execute:
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;
}
компиляция следующего кода в Visual Studio 2010 дает ошибку:
error C3867: 'GAME::events': function call missing argument list; use '&GAME::events' to create a pointer to member
использование & GAME:: события дает:
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
7 ответов
существует синтаксис C++ для указания на методы-члены класса, но вы не можете передать их API стиля C. C понимает вызовы функций и каждый нестатический метод объекта, принимая ваш events
в качестве примера выглядит такое мышление в терминах C:void events(void* this, int, int);
означает, что каждый метод, кроме стандартных аргументов, также получает this
указатель молча прошел.
чтобы сделать ваш events
C совместимость сделать это static void events(int, int);
. Таким образом, он будет следовать семантике вызова C - он не будет требуется this
указатель передается. Вы также должны каким-то образом передать свой объект этому обратному вызову каким-то другим способом (если вам нужны данные этого объекта в обратном вызове).
примеры кода, приведенные в других ответах, не описывают, как перенаправить обратный вызов на функцию-член для каждого объекта, возможно, с любым количеством объектов. Создание одноэлементного класса A ограничит ваш дизайн и не будет масштабироваться до нескольких окон glfw.
масштабируемое решение состоит в том, чтобы установить указатель пользователя окна glfw на ваш объект, а затем получить его в обратном вызове и вызвать функцию-член:
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);
}
Это решение короче и будет работает для любого количества окон.
Я также столкнулся с этой проблемой с другой функцией обратного вызова glfw, но я не хотел объявлять свой метод класса как static
, потому что мне нужно получить доступ к переменным-членам в рамках. Поэтому я попытался std::function
и std::bind
для предоставления мне возможности привязать метод экземпляра как функцию обратного вызова, но, к сожалению, это не вариант при работе с обратными вызовами C.
ответ на эту проблему также изложен в GLFW FAQ " как использовать методы C++ как обратные вызовы":
нельзя использовать обычные методы, как обратные вызовы, как GLFW-это библиотека c и не знает об объектах и указателях. Если вы хотите получение обратных вызовов объекта C++,используйте статические методы или регулярные функции в качестве обратных вызовов, хранить указатель на объект, который вы хотите вызовите в некотором месте, доступном из обратных вызовов, и используйте его для вызова методы на вашем объекте.
однако это побудило меня подать заявку одноэлементный шаблон для моего класса обратного вызова и интегрировать его следующим образом:
- метод обратного вызова моего класса по-прежнему статичен, поэтому его можно указать/использовать как обратный вызов glfw
- этот статический метод обратного вызова использует Синглтон и передает параметры обратного вызова методу экземпляра
- этот метод экземпляра фактически обрабатывает параметры обратного вызова, имея возможность доступа к члену переменные
вот как это выглядит:
// 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
};
и в моей основной.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
вдохновленный ответом N0vember, я представляю вам еще более общее и динамическое решение:
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;
};
};
}
в заголовочном файле сделайте события (int, int) статическим методом. Это решило проблему для меня.
class GAME
{
private:
bool running;
public:
GAME();
int execute();
static void events(int, int); //Changed here to static void
int loop();
int render();
};
у меня была такая же проблема и после прочтения этой темы я придумал аналогичное решение. Я думаю, так немного чище. Он основан на статическую функцию, но он вложен в класс, где мы все.
заголовок выглядит так:
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;
};
};
и исходный код:
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);
}
это полезное обсуждение возможных решений, которые помогли мне с той же проблемой, и я добавляю свое решение, если оно окажется полезным.
Постановка Задачи
мой сценарий более общий, чем те, которые рассматриваются BIC, L. Senionis и N0vember. В частности, мой вариант использования требует:
- как правило, данные экземпляра должны быть доступны для вызова
- много применений можно создать используя общее набор обработчиков ответов
- в приложении может быть создано любое количество окон
- набор обратных вызовов, прикрепленных к каждому окну, должен быть смешан и сопоставлен из определенной библиотеки возможных ответчиков.
Использование Предлагаемого Решения
простой одноэлементный дизайн больше не решает проблему. Вместо этого я предоставляю GLFWResponder
суперкласс, который обрабатывает всю сложность настройки. Чтобы использовать класс и присоединить ответ на окно, вот что требуется.
// 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);
Реализация Предлагаемого Решения
вот эскиз GLFWResponder
реализация, полностью функциональная, но с некоторыми задачами. Могут быть некоторые последствия для производительности, которые я еще не исследовал.
// 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"
}