指向一个函数,它是一个类成员- GLFW setKeycallback
Pointing to a function that is a class member - GLFW setKeycallback
我正在编写一个GLFW应用程序,其中我已经将函数调用包装到一个简单的类中。我在设置键回叫时遇到了问题。我的类定义为:
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
其他答案中提供的代码示例没有描述如何将回调重定向到可能具有任意数量对象的每个对象成员函数。让你的类成为单例将会限制你的设计,并且不能扩展到多个窗口。
可伸缩的解决方案是设置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 callback
- 这个静态回调方法使用单例并将回调参数传递给实例方法
- 这个实例方法实际处理回调参数,其好处是能够访问成员变量
是这样的:
// 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
};
和in my 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
有一个指向类成员方法的c++语法,但你不能将它们传递给C风格的API。C理解函数调用和每个非静态对象方法,以events
为例,在C术语中看起来像这样的思维:void events(void* this, int, int);
意味着除了标准参数之外的每个方法也获得一个this
指针,静默传递。
使您的events
C兼容,使其static void events(int, int);
。这样它将遵循C调用语义-它不需要传递this
指针。你还必须以某种其他方式将你的对象传递给这个回调(如果你在回调中需要这个对象的数据)。
我也有同样的问题,在阅读了这篇文章后,我想出了一个类似的解决方案。我觉得这样比较干净。它基于静态函数,但它嵌套在我们设置所有东西的类中。
页眉是这样的:
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);
}
受十一月答案的启发,我提出了更通用和动态的解决方案:
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();
};
这是对可能的解决方案的有用讨论,它帮助我解决了同样的问题,我添加了我的解决方案,以防它证明是有用的。
问题陈述
我的场景比BIC、l.s senionis和november处理的场景更一般。特别地,我的用例需要:
- 通常,实例的数据必须能够被回调 访问
- 可以使用一组通用的响应处理程序创建许多应用程序
- 在一个应用程序中,可以创建任意数量的窗口
- 附加到每个窗口的回调集应该从某个可能的响应器库中混合和匹配。
建议的解决方案用法
简单的单例设计不再解决问题。相反,我提供了一个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"
}
- 基于另一个成员参数将函数调用从类传递给它的一个成员
- C++ - 如何在结构向量中找到结构体一个成员的最大值?
- 无法将指向类的成员函数的函数指针作为参数传递给同一类的另一个成员函数
- 使用只有一个成员的工会的目的是什么?
- 访问从联合与另一个成员集复制的联合中的一个成员是否未定义或未指定?
- std::mutex作为一个成员变量对多个线程来说是安全的吗
- 使用线程从另一个成员函数调用一个Member函数
- 如何通过指针将模板成员函数传递给另一个成员函数
- 只有一个成员的匿名联盟
- 复制一个对象并使两者共享一个成员变量 (C++)
- 初始化指针或引用成员变量以指向另一个成员
- 在另一个成员函数中调用成员函数时'int'之前的预期主表达式
- C 将成员函数作为参数传递给另一个成员函数
- 将结构编写为二进制,其中一个成员是字符串
- 如何在另一个成员函数中修改具有常量返回类型的成员函数的返回值
- 从其后声明的另一个成员数据初始化成员数据是否为未定义行为
- 如何使一个成员变量等于在main()中设置的另一个成员变量
- 在另一个成员中动态添加类成员
- 在对结构数组的一个成员进行排序后,移动其成员的其余部分
- 将类成员函数作为参数从同一类的另一个成员函数传递