将C++对象方法作为SDL_EventFilter传递

Pass C++ object method as SDL_EventFilter

提问人:K. Russell Smith 提问时间:11/11/2023 最后编辑:wohlstadK. Russell Smith 更新时间:11/11/2023 访问量:120

问:

我正在尝试使用 SDL2 在 C++ 中创建游戏,当然我想检查窗口大小调整事件......因为我有一个类处理了所有与 SDL 相关的内容:Game

bool Game::SetupSDL()
{
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
    {
        std::cout << "SDL initialization failed: " << SDL_GetError() << std::endl;
        return false;
    }

    window = SDL_CreateWindow(
        "SDL",
        SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
        640, 480,
        SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE);

    if (window == NULL)
    {
        std::cout << "SDL window creation failed: " << SDL_GetError() << std::endl;
        return false;
    }

    if (TTF_Init() < 0)
    {
        return false;
    }

    renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    SDL_AddEventWatch(OnResize, window);

    return true;
}

初始化方式如下:OnResize

int Game::OnResize(void *data, SDL_Event *event)
{
    if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED)
    {
        SetScale();
        states[state]->OnResize(*this);
    }
    return 0;
}

但是,当我尝试编译它时,我得到以下信息:

error: cannot convert 'Game::OnResize' from type 'int (Game::)(void*, SDL_Event*)' to type 'SDL_EventFilter {aka int (*)(void*, SDL_Event*)}'
  SDL_AddEventWatch(this->OnResize, window);

因此,我似乎在将实例方法作为...尝试重铸它导致编译或运行时错误,我该如何解决这个问题?SDL_FilterEvent

C++ 回调 SDL

评论

3赞 wohlstad 11/11/2023
我不熟悉这个 API,但您可能需要传递静态方法(或自由函数)作为回调,并将实例指针 () 作为上下文传递。然后,当您获得回调时,将上下文指针转换回并调用您的方法。thisvoid*void*Game*
0赞 Some programmer dude 11/11/2023
非静态成员函数需要一个对象来调用(该对象位于函数内部)。C 函数如何知道要使用哪个对象?它甚至一开始就不知道 C++ 对象。相反,如果回调系统具有用户数据指针之类的内容,则可以将用户数据指针设置为指向对象,然后使用静态成员函数。此静态成员函数采用用户数据指针,将其转换为指向对象的指针,并调用实际的非静态成员函数。this
0赞 wohlstad 11/11/2023
阅读有关此 API 的更多信息。请看下面的回答。
0赞 wohlstad 11/11/2023
@user12002570 我看不出您建议的重复项实际上与此问题有何关系。OP 似乎知道如何传递全局函数。问题在于对这个特定的 API 使用非静态方法(我下面的答案解决了这个问题)。投票决定重新开放。
1赞 user12002570 11/11/2023
@wohlstad我已经重新开放了。

答:

0赞 Alex 11/11/2023 #1

您收到该错误是因为该语言不知道如何访问要调用该方法的类的正确实例。

您可以将 Game::OnResize 替换为 Game 类之外的自由浮动函数,该函数可以访问指向游戏实例的指针,沿 SDL 事件数据转发,也可以在 Game 类中使用静态方法。

如果你想把它全部保留在你的类中(就像我倾向于做的那样),你可以将 Game::OnResize 替换为一个静态方法,该方法可以访问指向你的游戏的指针,然后创建一个新的非静态方法来实际执行事件的处理。然后,静态方法可以将事件转发到非静态方法。

喜欢这个:

// =========================================================================
// game.h
// =========================================================================

class Game
{
    public:
    
        // public stuff...
        
    private:
    
        // private stuff...
        
        // replace OnResize with this:
        static int OnSDLEvent(void *data, SDL_Event *event);
        
        // new function:
        int HandleSDLEvent(void *data, SDL_Event *event);
};

// =========================================================================
// game.cpp
// =========================================================================

// somewhere at the top...
Game *g_Game{}; // global pointer to the game, only in this file

Game::Game()
{
    // let's make g_Game point to our actual game instance
    // in your game's constructor.

    // or it could be in Game::SetupSDL() or wherever...
    // it just needs to happen before adding your event watch.

    // this is so the static method has an instance to work with.
    g_Game = this;
}


// here's the static method that gets called by SDL
int Game::OnSDLEvent(void *data, SDL_Event *event)
{
    // just pass along the event data.
    return g_Game->HandleSDLEvent(data, event);
}

// and here's where you actually handle the event!
int Game::HandleSDLEvent(void *data, SDL_Event *event)
{
    if (event->type == SDL_WINDOWEVENT && event->window.event == SDL_WINDOWEVENT_RESIZED)
    {
        SetScale();
        states[state]->OnResize(*this);
    }
    return 0;
}

// finally, feed SDL with our static method we created
bool Game::SetupSDL()
{
    // setup code here...

    // now we pass in our static method
    SDL_AddEventWatch(OnSDLEvent, window);

    // etc.
}

评论

0赞 wohlstad 11/11/2023
一般来说,在上下文中使用全局不是一个好主意。如果您有 2 个实例(并且您希望每个实例都获得其回调)怎么办?Game*Game
3赞 wohlstad 11/11/2023 #2

问题:

为了向 SDL_AddEventWatch 注册事件回调,
您需要以函数的形式提供类型为 SDL_EventFilter

int (*SDL_EventFilter)(void *userdata, SDL_Event *event);

这应该是一个自由函数,或者是一个类的静态方法。它不能是非静态方法,因为此类方法需要实例(指针)才能调用。Gamethis

解决方案:

为了使用非静态方法作为回调,您可以利用该参数作为上下文。userdata

注册事件处理程序时,将 (即 ) 与静态方法一起传递:thisGame*userdata

static int Game::OnResizeEventCallback(void *userdata, SDL_Event *event);  // see implementation below

// ...

//---------------------------------------vvvvv-
SDL_AddEventWatch(OnResizeEventCallback, this);

然后当你得到回调时,转换回 ,并调用非静态方法:userdataGame*

static int Game::OnResizeEventCallback(void *userdata, SDL_Event *event)
{
    // Retrieve your instance:
    Game* pThis = reinterpret_cast<Game*>(userdata);
    // Call your non-static method with it:
    pThis->OnResize(event);
}

评论

1赞 Alex 11/11/2023
这个答案是正确的。我假设将窗口指针传递到 SDL_AddEventWatch 中,如 OP 的代码所示,是正确的 - 事实并非如此。