如何为不同的类glfwSetKeyCallback?

时间:2014-02-15 15:45:34

标签: c++ opengl glfw

我使用 GLFW ,我有不同的代表我的应用程序中的不同状态和一个状态管理类。 我想使用GLFW接收键输入并将其传递给当前状态(因此转到另一个类)。

我能想到的唯一方法就是给每个班级一个自己的 keycallback -function并使用 glfwSetKeyCallback(window,keyCallback);

keycallback(GLFWwindow *window, int key, int scancode, int action, int mods)

它不起作用

cannot convert 'IntroState::handleKeyEvent' from type 'void (IntroState::)(GLFWwindow*, int, int, int, int)' to type 'GLFWkeyfun {aka void (*)(GLFWwindow*, int, int, int, int)}'
     glfwSetKeyCallback(m_manager->window, handleKeyEvent);

人们推荐这样的东西:

void GLFWCALL functionname( int key, int action );

但GLFWCALL-macro已从GLFW(Official Note)中移除。


我读过你可以使用静态函数。

static void keyCallback(GLFWwindow*, int, int, int);

但是当回调函数是静态的时候,我需要访问非静态成员函数并遇到一些问题。


实际问题:

如何让当前的“state”类获得GLFW的键输入? 像

states.back()->handlekeyEvent(int, int, int);

5 个答案:

答案 0 :(得分:10)

当你退后一步并且根本不在课堂上思考时,事情变得容易多了。类也是类型,类型并不真正描述特定的状态。我认为你的实际意思是实例如果一个具有不同内部状态的类。

因此,您的问题就是将回调与您的州相关联。在您的帖子中,您编写了以下不完整回调签名:

keycallback(GLFWwindow *window, int key, int scancode, int action, int mods);

缺少某些东西,即它返回的类型。哪个是void所以完整的签名是

void GLFWCALL keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);

其中GLFWCALL是未进一步指定的宏,它扩展为其他类型签名限定符。您不必再关心它了,这是唯一可以作为回调有效传递的类型签名。现在你想写一些类似

的图像
class FooState
{
    void GLFWCALL keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods);  
};

这是什么类型签名?好吧,当你真正实现它时,你就是把它写下来了

void GLFWCALL FooState::keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);  

请看这个额外的FooState::。这不仅仅是对名称或命名空间的一些补充。它实际上是一个类型修饰符。就好像你在函数中添加了另一个参数(事实上发生了这种情况),并且该参数是类实例的引用或指针(在C ++的情况下是一个指针)。该指针通常称为this。因此,如果您通过C编译器(不知道类)的眼睛来看这个函数,并且用C语言编写GLFW,那么签名实际上就像

void GLFWCALL keycallback(
    FooState *this,
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods);  

很明显,这不符合GLFW的需求。所以你需要的是某种包装器,它可以在那里获得这个额外的参数。你从哪里获得额外的参数?那么这将是你必须管理的程序中的另一个变量。

现在我们可以使用静态类成员。在类的范围内static表示由类的名称空间中的所有类和函数实例共享的全局变量,但不适用于它的实例(因此从C编译器的角度来看, “只是常规函数而没有其他参数。”

首先是一个为我们提供统一界面的基类

// statebase.h
class StateBase
{
    virtual void keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods) = 0; /* purely abstract function */

    static StateBase *event_handling_instance;
    // technically setEventHandling should be finalized so that it doesn't
    // get overwritten by a descendant class.
    virtual void setEventHandling() { event_handling_instance = this; }

    static void GLFWCALL keycallback_dispatch(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods)
    {
        if(event_handling_instance)
            event_handling_instance->keycallback(window,key,scancode,action,mods);
    }    
};

// statebase.cpp
#include "statebase.h"
// funny thing that you have to omit `static` here. Learn about global scope
// type qualifiers to understand why.
StateBase * StateBase::event_handling_instance;

现在纯粹抽象的虚函数就是这里的诀窍:StateBase通过使用虚拟调度引用从event_handling_instance派生的任何类,它最终会被函数实现调用该实例的类类型。

所以我们现在可以声明FooStateStateBase派生并像往常一样实现虚拟回调函数(但省略了GLFWCALL宏)

class FooState : BaseState
{
    virtual void keycallback(
        GLFWwindow *window,
        int key,
        int scancode,
        int action,
        int mods);  
};
void FooState::keycallback(
    GLFWwindow *window,
    int key,
    int scancode,
    int action,
    int mods)
{
    /* ... */
}

要在GLFW中使用它,请将静态调度程序注册为回调

glfwSetKeyCallback(window, StateBase::keycallback_dispatcher);

要选择特定实例作为活动回调处理程序,请调用其继承的void StateBase::setEventHandling()函数。

FooState state;
state.setEventHandling();

一些建议的离别词:

如果您不熟悉C ++中OOP的这些概念以及类型和实例之间复杂的交互方式,则不应该进行任何OOP编程,尚未。 OOP曾被声称能让事情变得更容易理解,而事实上,用C ++实现的精明方式实际上会让一个大脑受到伤害并且扩散非常糟糕的风格。

以下是您的练习:尝试在不使用OOP的情况下实现您尝试实现的目标。只需使用没有成员函数的structs(这样它们就不会变成伪装的类),试着想一想 flat 数据结构以及如何使用它们。这是一项非常重要的技能,直接跳入OOP的人不会训练很多。

答案 1 :(得分:4)

您需要这样做才能称为closure(提供引用非本地存储的必要上下文)。您无法访问传统C回调中的类成员,因为无法满足C ++类函数的调用约定(即this指针的附加要求)。

大多数框架通过允许您提供一些void *用户数据来解决这个问题,这些用户数据可以直接传递给您的回调,也可以在触发回调时查询。您可以将此void *转换为您想要的任何内容,在这种情况下是指向类对象的指针,然后调用属于该对象的成员函数。

顺便说一下,GLFWCALL应该对这个讨论一无所知。这是一个定义平台默认调用约定的宏。它永远不会帮助您在C ++中调用成员函数,其主要目的是简化DLL导入/导出等操作。

答案 2 :(得分:0)

为GLFW制作单独的控制器程序;让它接收事件并将它们传递给当前位于状态堆栈顶部的任何状态。 (我通常在应用程序主循环中直接使用它。)

答案 3 :(得分:0)

我想出了一个将类粘合到glfw回调的解决方案。它相当灵活,但是只允许您注册一次类,而不能重新注册。

在函数中将对我们类的静态引用包装为静态函数变量。将引用作为参数,进行设置,然后返回lambda的指针。请注意,lambda不需要捕获任何内容,因为我们的类引用是静态的,但只能在函数中访问:

void  (*build_framebuffer_callback(rendering::renderer& r))(GLFWwindow*, int, int)
{
    static rendering::renderer& renderer = r;

    void (*callback)(GLFWwindow*, int, int) = ([](GLFWwindow* window, int width, int height) {
        renderer.resize(width, height);
    });

    return callback;
}

签名与以下glfw回调标头匹配:

void framebuffer_size_callback(GLFWwindow* window, int width, int height);

然后,您可以执行以下操作:

glfwSetFramebufferSizeCallback(window, build_framebuffer_callback(renderer));

以上内容适用于我的渲染器类,但我认为这也适用于输入。然后,您将在输入类上创建一个函数,该函数允许lambda设置需要设置的任何内容。

答案 4 :(得分:0)

什么@andon-m-coleman said

    auto keyCallback = []( GLFWwindow* window, int key, int scancode, int action, int mods ) {
        auto me = (Window*)glfwGetWindowUserPointer( window );
        me->KeyCallback( window, key, scancode, action, mods );
    };
    glfwSetWindowUserPointer( window, this );
    glfwSetKeyCallback( window, keyCallback );