【问题标题】:Weird bug with WndProc and DispatchMessage. Member Functions don't exist?WndProc 和 DispatchMessage 的奇怪错误。成员函数不存在?
【发布时间】:2011-10-13 20:10:51
【问题描述】:

所以在我的 Window 类中获取 WM_KEYDOWN 消息时出现了一个奇怪的错误。

我有一个全局 WndProc 函数,它确定它是哪个窗口实例并将消息发送到它自己的本地 WndProc 函数。

//Every Windows Message will hit this function. 
LRESULT CALLBACK GlobalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {

    //Get the Window Instance from the userdata
    Window* targetWindow = (Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);

    //If no window exists, then it must be the first time so we should set it
    if (!targetWindow) {
        //First let's try and extract the Window instance pointer from the lparam
        CREATESTRUCT* createStruct = (CREATESTRUCT*)lparam;
        if (createStruct) {
            targetWindow = (Window*)createStruct->lpCreateParams;
            SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)targetWindow);
        }
        else {
            //It was some other message which we just can't deal with right now
            return DefWindowProc(hwnd, msg, wparam, lparam);
        }
    }

    //Now we can pipe it to the Window's local wnd proc function
    return targetWindow->LocalWndProc(hwnd, msg, wparam, lparam);
}

而我的本地 wndproc 看起来像这样:

LRESULT CALLBACK Window::LocalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
    switch (msg) {

    case WM_KEYDOWN:
        ToggleFullScreen(!m_fullScreen);
        return 0;
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
        break;

    case WM_CLOSE:
        PostQuitMessage(0);
        return 0;
        break;

    default:
        return DefWindowProc(hwnd, msg, wparam, lparam);
    }
}

所以现在很简单。如果按下任何键,窗口应调用其成员函数 ToggleFullScreen。

现在由于某种原因,当我运行此程序并按键盘上的任意键时,出现异常错误:

Athena_Debug.exe 中 0x77c015ee 处的未处理异常:0xC0000005:访问冲突读取位置 0x0000012d。

使用调用堆栈:

ntdll.dll!77c015ee()    
ntdll.dll!77bf015e()    
user32.dll!7588788a()   

Athena_Debug.exe!Athena::Window::HandleWindowsMessage() 第 195 行 + 0xc 字节 C++ Athena_Debug.exe!Athena::AthenaCore::StartEngine() 第 96 行 + 0x12 字节 C++ Athena_Debug.exe!WinMain(HINSTANCE__ * hInstance, HINSTANCE__ * hPrevInstance, char * lpCmdLine, int nCmdShow) 第 44 行 C++ Athena_Debug.exe!__tmainCRTStartup() 第 547 行 + 0x2c 字节 C Athena_Debug.exe!WinMainCRTStartup() 第 371 行 C kernel32.dll!7702339a()

它实际中断的行是位于此处的 DispatchMessage(&msg) 行:

MSG Window::HandleWindowsMessage() {
    MSG msg;
    ZeroMemory(&msg, sizeof(MSG));
    if (m_realTime) {
        PeekMessage(&msg, m_hwnd, 0, 0, PM_REMOVE);
    }
    else {
        GetMessage(&msg, m_hwnd, 0, 0);
    }

    if (msg.message != WM_NULL) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg;
}

所以有趣的是,如果我注释掉 ToggleFullScreen 而是在其中放置一个 OutputDebugTrace,它就可以正常工作并输出调试跟踪。似乎无法解析实例的功能?如果我在 ToggleFullScreen 中注释掉所有内容,它仍然会崩溃。如果我创建一个什么都不做的全新函数并调用它以响应 WM_KEYDOWN 它仍然会出错。

有人知道为什么会发生这种情况,或者对我可以如何解决它有一些想法吗?

谢谢!

【问题讨论】:

  • 你确定 this 指针指向的东西在 Window::LocalWndProc 中有效吗?
  • 不是问题,但您应该在 WM_CLOSE 中使用 DestroyWindow() 而不是 PostQuitMessage()
  • 你不应该使用 SetWindowLong,因为它现在是空的 - 你应该测试 WM_CREATE 消息。
  • @Anders 谢谢,很高兴知道!

标签: c++ winapi


【解决方案1】:

我猜你从 GetWindowLongPtr 获得了错误的指针,因为它不是由 SetWindowLongPtr 设置的。正如我在评论中所写,你不应该测试它是否为空(我不知道默认情况下有什么,但我不会假设任何东西。),但在你收到 WM_CRERATE 时设置它(这保证是第一条消息发送到窗口)。您是否测试过是否实际调用了 SetWindowLongPtr?

其他消息不依赖于 Window 类的任何内容,因此它们在错误的 this 指针下运行良好 - 为错误的指针调用方法是错误的,但如果方法实际上没有使用它,它仍然会工作得很好。

编辑:

另一个可能的原因:您为什么不使用 PeekMessage 的结果值?我看到您正在测试 WM_NULL,此时它应该为 0(您正在清零内存),但 PeekMessage 不保证即使它没有检索任何内容,它也不会触及 MSG 结构。应始终使用使用 PeekMessage 结果。另外,此时归零内存的效率相当低。

【讨论】:

  • 谢谢!这确实是问题所在。虽然我实际上首先得到了 WM_NCCREATE,所以然后我 SetWindowLongPtr 。它现在按预期工作。
  • WM_CREATE 不是第一个 - WM_NCCREATE 在它之前。
  • 这是错误的,WM_NCCREATE不保证是第一条消息!
  • @Anders:在接收第一条消息之前设置自定义窗口过程的唯一安全方法是使用SetWindowsHookEx 安装WH_CBT 挂钩,并在处理程序中为@ 执行所有内务处理987654322@。该钩子安装了一个回调,该回调在创建通过HWND 引用的内核对象和第一次调用窗口过程之间执行。我还建议使用SetProp 而不是GWLP_USERDATA
【解决方案2】:

第一条消息不必是 WM_NCCREATE(或 WM_CREATE),您必须准备好在任何创建消息之前获取 WM_SIZE 或 WM_GETMINMAXINFO(可能还有其他消息)!

在您的代码中,您可以将 if (createStruct) { 更改为 if (WM_NCCREATE==msg && createStruct) {...

您可以在 Raymond Chen 的scratch program 中看到这一点。

【讨论】:

    【解决方案3】:

    您的 WindowProc 回调应该看起来更像这样:

    //Every Windows Message will hit this function.
    LRESULT CALLBACK GlobalWndProc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
    {
        Window* targetWindow;
    
        if (msg == WM_CREATE)
        {
            //extract the Window instance pointer from the lparam
            CREATESTRUCT* createStruct = (CREATESTRUCT*)lparam;
    
            targetWindow = (Window*)createStruct->lpCreateParams;
            SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)targetWindow);
        }
        else
        {
            //Get the Window Instance from the userdata
            targetWindow = (Window*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
        }
    
        if (targetWindow)
        {
            //Now we can pipe it to the Window's local wnd proc function
            return targetWindow->LocalWndProc(hwnd, msg, wparam, lparam);
        }
    
        //It was some other message which we just can't deal with right now
        return DefWindowProc(hwnd, msg, wparam, lparam);
    } 
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-12-24
      • 2021-04-27
      • 1970-01-01
      • 1970-01-01
      • 2011-10-11
      相关资源
      最近更新 更多