当前位置: 首页 > 知识库问答 >
问题:

如何为不同的类设置glfwSetKeyCallback?

刘凡
2023-03-14

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

我能想到的唯一方法是给每个类一个自己的keycallback函数,并使用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宏已从GLFW中删除(官方注释)。

我已经读到你可以使用静态函数。

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

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

如何使当前的“state”类从GLFW获取键输入?喜欢

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

共有3个答案

花稳
2023-03-14

@andon-m-coleman说了什么

    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 );
寇丰
2023-03-14

您需要的是闭包(提供引用非本地存储的必要上下文)。您无法在传统的C回调中访问类成员,因为无法满足C类函数的调用约定(即添加了指针的要求)。

大多数框架都允许您提供一些void*用户数据,这些数据可以直接传递给回调,也可以在触发回调时查询。您可以将此void*强制转换为任何您想要的对象,在本例中是指向类对象的指针,然后调用属于该对象的成员函数。

顺便说一句,GLFWCALL真的不应该给这个讨论增加什么。这是一个定义平台默认调用约定的宏。它永远不会帮助您在C中调用成员函数,它的主要目的是简化DLL导入/导出之类的操作。

范金鑫
2023-03-14

当你退一步,在课堂上完全不思考的时候,事情会变得容易得多。类也是一种类型,类型并不真正描述特定的状态。我认为你实际上指的是一个具有不同内部状态的类的实例。

因此,您的问题变成将回调与您的状态关联起来。在您的帖子中,您写了以下不完整的回调签名:

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编译器(不知道类)的角度来看这个函数,并且GLFW是用C编写的,那么签名实际上看起来像

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通过使用虚拟调度引用的,它最终将被调用的类类型的函数实现那个实例。

因此,我们现在可以声明FooState是从StateBase派生的,并像往常一样实现虚拟回调函数(但省略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而不使用成员函数(这样它们就不会伪装成类),试着想出平面数据结构以及如何使用它们。这是一项非常重要的技能,直接进入OOP的人不需要进行大量训练。

 类似资料:
  • 我试图部署一个Vue应用程序,它有一个单独的后端,并将托管在不同的域。例如: 喵喵。猫xyz(应用程序) 现在,在之后,我试图通过运行在本地预览它,而应用程序正在本地主机上断开:5000。但是,问题在于它没有在当前endpoint(即本地的,服务器的)发送API请求。我尝试了如下配置CORS vue.config.js .env.development 请注意,我使用axiox。这是我的axios

  • 我有两个日期选择器“一个范围”,第一个选择器是“pre_from”日期,第二个是“pre_to”日期。但是,我想在这里做的是将“pre_to”选择器minDate发送到=“pre_from”selectedate。此外,我还想将“pre_to”选择器设置为“pre_from”所选月份的最后一天。 我已经向“pre_from”datepicker的“onclose”函数添加了一段代码,该代码应该设置

  • 我对列有网格。在第二列中,我必须组合框(例如在第一行和第三行)。如何为它们设置不同的存储空间?第二个组合框的值取决于第一个组合框 这是一张图片 我看到一个例子,但是有不同的列,而不是行

  • 我正在从事两个不同的项目,都使用不同的maven文件 当我在项目之间切换时,它会重新下载存储库文件夹中的所有maven依赖项,删除以前项目的依赖项 是否有任何方法可以使用两个,并为两个项目维护不同的存储库。

  • 问题内容: 我想在不使用设置器的情况下将值插入变量。如果有可能怎么办。 这是一个例子 现在我有一个包含,和的函数。 我试图使用一种通用方法将值设置为Object(objectOfClass),而值我已经在相应的variable()中通过了()。 问题答案: 此代码未经测试。你可以试试看 要导入的类 方法

  • 我有一个使用StaggeredGridLayoutManager的RecyclerView,2列例如,我想为第一列和第二列设置不同的背景,如何做到这一点,提前感谢。