Указание на функцию, которая является членом класса - glfw setKeycallback - PullRequest
23 голосов
/ 06 октября 2011

Я пишу приложение glfw, в котором я обернул функцию callse в простой класс.У меня проблемы с установкой обратного вызова ключа.Мой класс определен как:

class GAME 
{
private:
    bool running;
public:
    GAME();
    int execute();
    void events(int, int);
    int loop();
    int render();
};

Функция выполнения:

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 :: events дает:

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 ]

36 голосов
/ 22 февраля 2015

Примеры кода, приведенные в других ответах, не описывают, как перенаправить ваш обратный вызов в функцию-член для каждого объекта, возможно, с любым количеством объектов. Если ваш класс будет единичным, это ограничит ваш дизайн и не увеличит масштаб до нескольких окон 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);
}

Это решение короче и будет работать для любого количества окон.

18 голосов
/ 06 июня 2013

Я также столкнулся с этой проблемой с другой функцией обратного вызова glfw, но я не хотел объявлять мой метод класса как static, потому что мне нужно было получить доступ к переменным-членам внутри. Поэтому я попытался std::function и std::bind дать мне возможность привязать метод экземпляра в качестве функции обратного вызова, но, к сожалению, это не вариант при работе с обратными вызовами Си.

Ответ на эту проблему также изложен в FAQ GLFW «Как использовать методы C ++ в качестве обратных вызовов» :

Вы не можете использовать обычные методы в качестве обратных вызовов, так как GLFW - это библиотека C и не знает об объектах и ​​указателях. Если вы хотите получать обратные вызовы к объекту C ++, использовать статические методы или обычные функции в качестве обратных вызовов , сохранять указатель на объект, который вы хотите позвонить в какое-то место, доступное из обратных вызовов, и использовать его для вызова методы на вашем объекте.

Однако это побудило меня применить шаблон Singleton для моего класса обратного вызова и интегрировать его следующим образом:

  • метод обратного вызова моего класса все еще статичен, поэтому его можно указать / использовать как обратный вызов glfw
  • этот метод статического обратного вызова использует singleton и передает параметры обратного вызова методу экземпляра
  • этот метод экземпляра фактически обрабатывает параметры обратного вызова с преимуществом возможности доступа к переменным-членам

Вот как это выглядит:

// 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
};

и в моем 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
13 голосов
/ 06 октября 2011

Существует синтаксис C ++ для указания на методы членов класса, но вы не можете передать их в API стиля C. C понимает вызовы функций, и каждый нестатический объектный метод, на примере вашего events, выглядит следующим образом в терминах C: void events(void* this, int, int); означает, что каждый метод, кроме стандартных аргументов, также получает указатель this, передаваемый без вывода сообщений. .

Чтобы сделать events C-совместимым, сделайте его static void events(int, int);. Таким образом, он будет следовать семантике вызова C - он не потребует передачи указателя this. Вы также должны каким-то образом передать свой объект этому обратному вызову (если вам нужны данные этого объекта в обратном вызове).

2 голосов
/ 29 августа 2016

Вдохновленный ответом Ноября, я представляю вам еще более общее и динамичное решение:

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;
        };
    };
}
1 голос
/ 11 декабря 2016

У меня была такая же проблема, и после прочтения этой темы я нашел похожее решение.Я думаю, что так немного чище.Он основан на статической функции, но он вложен в класс, в котором мы все устанавливаем.

Заголовок выглядит следующим образом:

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);
}
1 голос
/ 17 июля 2015

В заголовочном файле сделать события (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();
};
0 голосов
/ 01 мая 2017

Это полезное обсуждение возможных решений, которые помогли мне решить ту же проблему, и я добавляю свое решение на случай, если оно окажется полезным.

Постановка задачи

Мой сценарий более общий, чем сценарии, рассмотренные BIC, L.Senionis и N0mber. В частности, мой вариант использования требует:

  • Как правило, данные экземпляра должны быть доступны для обратного вызова
  • Многие приложения могут быть созданы с использованием общего набора обработчиков ответов
  • В приложении может быть создано любое количество окон
  • Набор обратных вызовов, прикрепленных к каждому окну, следует смешивать и сопоставлять с определенной библиотекой возможных респондентов.

Использование предлагаемого решения

Простой синглтон-дизайн больше не решает проблему. Вместо этого я предоставляю суперкласс 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, полностью функциональной, но с некоторыми TODO. Могут быть некоторые последствия для производительности, которые я еще не исследовал.

// 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"
}
...