应用程序退出时线程无法退出 - C++

发布于 2024-10-05 07:29:30 字数 1957 浏览 7 评论 0原文

我的应用程序创建一个轮询 Windows 消息的线程。当需要关闭时,我的应用程序会发送 WM_QUIT 消息。

在应用程序线程中,这就是我尝试关闭的方式:

if ( _hNotifyWindowThread != NULL )
{
    ASSERT(_pobjNotifyWindow != NULL);

    ::SendMessage( _pobjNotifyWindow->m_hWnd, WM_QUIT, 0, 0 );
    ::WaitForSingleObject( _hNotifyWindowThread, 50000L );
    ::CloseHandle( _hNotifyWindowThread ); // <-- PC never gets here.
    _hNotifyWindowThread = NULL;
}

这是在我的线程函数中运行的消息泵:

// Start the message pump...
while ( (bRetVal = ::GetMessage(
    &msg,                           // message structure
    _pobjNotifyWindow->m_hWnd,      // handle to window whose messages are to be retrieved
    WM_DEVICECHANGE,                // lowest message value to retrieve
    WM_DEVICECHANGE                 // highest message value to retrieve
    )) != 0 )
{
    switch ( bRetVal )
    {
    case -1:                        // Error generated in GetMessage.
        TRACE(_T("NotifyWindowThreadFn : Failed to get notify window message.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
        return ::GetLastError();
        break;

    default:                        // Other message received.
        ::TranslateMessage( &msg );
        ::DispatchMessage( &msg );
        break;
    }
}

delete _pobjNotifyWindow;           // Delete the notify window.

return msg.wParam;                  // Return exit code.

GetMessage 的 Microsoft 文档指出:

如果函数检索到 WM_QUIT 消息,则返回值为零。

请注意,无论您为 wMsgFilterMin 和 wMsgFilterMax 指定哪个值,GetMessage 始终检索 WM_QUIT 消息。

如果是这种情况,那么我希望调用 GetMessage 来检索 WM_QUIT 消息返回 0。但是,调试让我相信没有收到该消息适当地。奇怪的是,我可以在 WndProc 函数中放置一个断点,并且它似乎收到了 WM_QUIT 消息。

我做错了什么?我应该使用不同的函数在线程之间发布消息吗?谢谢。

My application creates a thread that polls for Windows messages. When it is time to close down, my application sends the WM_QUIT message.

In the application thread, this is how I am attempting to shut things down:

if ( _hNotifyWindowThread != NULL )
{
    ASSERT(_pobjNotifyWindow != NULL);

    ::SendMessage( _pobjNotifyWindow->m_hWnd, WM_QUIT, 0, 0 );
    ::WaitForSingleObject( _hNotifyWindowThread, 50000L );
    ::CloseHandle( _hNotifyWindowThread ); // <-- PC never gets here.
    _hNotifyWindowThread = NULL;
}

This is the message pump running in my thread function:

// Start the message pump...
while ( (bRetVal = ::GetMessage(
    &msg,                           // message structure
    _pobjNotifyWindow->m_hWnd,      // handle to window whose messages are to be retrieved
    WM_DEVICECHANGE,                // lowest message value to retrieve
    WM_DEVICECHANGE                 // highest message value to retrieve
    )) != 0 )
{
    switch ( bRetVal )
    {
    case -1:                        // Error generated in GetMessage.
        TRACE(_T("NotifyWindowThreadFn : Failed to get notify window message.\r\n\tError: %d\r\n\tFile: %s\r\n\tLine: %d\r\n"), ::GetLastError(), __WFILE__, __LINE__);
        return ::GetLastError();
        break;

    default:                        // Other message received.
        ::TranslateMessage( &msg );
        ::DispatchMessage( &msg );
        break;
    }
}

delete _pobjNotifyWindow;           // Delete the notify window.

return msg.wParam;                  // Return exit code.

The Microsoft documentation for GetMessage states:

If the function retrieves the WM_QUIT message, the return value is zero.

Note that GetMessage always retrieves WM_QUIT messages, no matter which values you specify for wMsgFilterMin and wMsgFilterMax.

If this is the case, then I would expect a call to GetMessage that retrieves the WM_QUIT message to return 0. However, debugging leaves me to believe that the message is not received properly. What is odd is that I can place a breakpoint in the WndProc function, and it seems to get the WM_QUIT message.

What am I doing wrong? Should I be using a different function for posting messages between threads? Thanks.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

夏有森光若流苏 2024-10-12 07:29:30

这是完整的答案(我几乎可以肯定):

替换

::SendMessage( _pobjNotifyWindow->m_hWnd, WM_QUIT, 0, 0 ); 

::PostMessage( _pobjNotifyWindow->m_hWnd, WM_CLOSE, 0, 0 ); 

替换

 ( (bRetVal = ::GetMessage( &msg, _pobjNotifyWindow->m_hWnd, WM_DEVICECHANGE, WM_DEVICECHANGE )) != 0 ) 

( (bRetVal = ::GetMessage( &msg, NULL ,0 ,0 )) != 0 )

在您的 WindowsProcedure 中:

 case WM_CLOSE : DestroyWindow( hWnd ); break; //can be return 

 case WM_DESTROY : PostQuitMessage( 0 ); 

This the complete answer (I'm almost sure):

replace

::SendMessage( _pobjNotifyWindow->m_hWnd, WM_QUIT, 0, 0 ); 

with

::PostMessage( _pobjNotifyWindow->m_hWnd, WM_CLOSE, 0, 0 ); 

replace

 ( (bRetVal = ::GetMessage( &msg, _pobjNotifyWindow->m_hWnd, WM_DEVICECHANGE, WM_DEVICECHANGE )) != 0 ) 

with ( (bRetVal = ::GetMessage( &msg, NULL ,0 ,0 )) != 0 )

In your WindowsProcedure :

 case WM_CLOSE : DestroyWindow( hWnd ); break; //can be return 

 case WM_DESTROY : PostQuitMessage( 0 ); 
如梦亦如幻 2024-10-12 07:29:30

虽然我对 WinAPI 的了解有限,但 WM_QUIT 似乎很特殊,并不意味着像其他消息一样发布。

根据Raymond Chen

与 WM_PAINT、WM_MOUSEMOVE 和 WM_TIMER 消息一样,WM_QUIT 消息不是“真正的”发布消息。相反,它是系统生成的消息之一,就好像它已发布一样,即使它没有发布。

当线程调用 PostQuitMessage 时,队列状态中会设置一个标志,表示“如果有人请求消息并且没有发布消息,则生成 WM_QUIT 消息”。这就像其他“虚拟发布”消息一样。

PostThreadMessage 只是将消息放入线程队列中(是真实的,而不是虚拟的),因此它不会得到真正的 PostQuitMessage 触发的任何特殊处理。

所以您可能应该使用 PostQuitMessage。

当然,可能有一些方法可以解决当前的奇怪行为(根据其他答案)。但考虑到 WM_QUIT 的描述很特殊,您可能还是想使用 PostQuitMessage。

While my knowledge of the WinAPI has limits, it seems WM_QUIT is special and not meant to be posted like other messages.

According to Raymond Chen:

Like the WM_PAINT, WM_MOUSEMOVE, and WM_TIMER messages, the WM_QUIT message is not a "real" posted message. Rather, it is one of those messages that the system generates as if it were posted, even though it wasn't.

.

When a thread calls PostQuitMessage, a flag in the queue state is set that says, "If somebody asks for a message and there are no posted messages, then manufacture a WM_QUIT message." This is just like the other "virtually posted" messages.

.

PostThreadMessage just places the message in the thread queue (for real, not virtually), and therefore it does not get any of the special treatment that a real PostQuitMessage triggers.

So you should probably be using PostQuitMessage.

Of course, there may be ways to work around the current odd behavior (as per other answers). But given the description of WM_QUIT being special, you may want to use PostQuitMessage anyway.

何处潇湘 2024-10-12 07:29:30

这段代码有两个问题。

  1. ::GetMessage() 不会停止,因为您将 hWnd 参数与 NULL 以外的其他参数一起使用。您需要获取线程消息以使::GetMessage()返回0
  2. 按照(1)中的逻辑,您需要使用 ::PostThreadMessage() 发布消息,将其放入线程的消息队列中。

的简写,这一事实很好地说明了所有这一切

::PostThreadMessage(::GetCurrentThreadId(), WM_QUIT, status, 0);

::PostQuitMessage(status)EDIT

:似乎人们已经被引导认为 ::PostThreadMessage(...,WM_QUIT,...); 不起作用,因为它没有获得设置由 QS_QUIT 标志设置的特殊处理>::PostQuitMessage()。如果是这种情况,则无法将 WM_QUIT 发送到另一个线程的消息队列。这是它无论如何都有效的证据。

特别要注意常量 Use_PostQuitMessageGetMessage_UseWindowHandle。请随意更改值并使用代码。它的工作原理就像我的答案中所宣传的那样,只是我在尝试之前错误地使用了 ::GetCurrentThread() 而不是 ::GetCurrentThreadId()

#include <Windows.h>
#include <iomanip>
#include <iostream>

namespace {

        // Doesn't matter if this is 'true' or 'false'.
    const bool Use_PostQuitMessage        = false;

        // Setting this to 'true' prevents the application from closing.
    const bool GetMessage_UseWindowHandle = false;

    void post_quit_message ()
    {
        if ( Use_PostQuitMessage ) {
            ::PostQuitMessage(0);
        }
        else {
            ::PostThreadMessageW(::GetCurrentThreadId(), WM_QUIT, 0, 0);
        }
    }

    ::BOOL get_message ( ::HWND window, ::MSG& message )
    {
        if ( GetMessage_UseWindowHandle ) {
            return (::GetMessageW(&message, window, 0, 0));
        }
        else {
            return (::GetMessageW(&message, 0, 0, 0));
        }
    }

    ::ULONG __stdcall background ( void * )
    {
            // Allocate window in background thread that is to be interrupted.
        ::HWND window = ::CreateWindowW(L"STATIC", 0, WS_OVERLAPPEDWINDOW,
            0, 0, 512, 256, 0, 0, ::GetModuleHandleW(0), 0);
        if ( window == 0 ) {
            std::cerr << "Could not create window." << std::endl;
            return (EXIT_FAILURE);
        }

            // Process messages for this thread's windows.
        ::ShowWindow(window, SW_NORMAL);
        ::MSG message;
        ::BOOL result = FALSE;
        while ((result = get_message(window,message)) > 0)
        {
                // Handle 'CloseWindow()'.
            if ( message.message == WM_CLOSE )
            {
                post_quit_message(); continue;
            }
                // Handling for 'ALT+F4'.
            if ((message.message == WM_SYSCOMMAND) &&
                (message.wParam == SC_CLOSE))
            {
                post_quit_message(); continue;
            }
                // Dispatch message to window procedure.
            ::TranslateMessage(&message);
            ::DispatchMessageW(&message);
        }
            // Check for error in 'GetMessage()'.
        if ( result == -1 )
        {
            std::cout << "GetMessage() failed with error: "
                << ::GetLastError() << "." << std::endl;
            return (EXIT_FAILURE);
        }
        return (EXIT_SUCCESS);
    }

}

int main ( int, char ** )
{
        // Launch window & message pump in background thread.
    ::DWORD id = 0;
    ::HANDLE thread = ::CreateThread(0, 0, &::background, 0, 0, &id);
    if ( thread == INVALID_HANDLE_VALUE ) {
        std::cerr << "Could not launch thread." << std::endl;
        return (EXIT_FAILURE);
    }

        // "do something"...
    ::Sleep(1000);

        // Decide to close application.
    ::PostThreadMessageW(id, WM_QUIT, 0, 0);

        // Wait for everything to shut down.
    ::WaitForSingleObject(thread, INFINITE);

        // Return background thread's success code.
    ::DWORD status = EXIT_FAILURE;
    ::GetExitCodeThread(thread,&status);
    return (status);
}

PS

实际测试 ::PostThreadMessage(::GetCurrentThreadId(),...); 的单线程使用调用 ::background(0) ; 在 main 中而不是启动线程。

There are 2 problems with this code.

  1. ::GetMessage() doesn't stop because you're using the hWnd parameter with something else than NULL. You need to fetch the thread messages to get ::GetMessage() to return 0.
  2. Following on the logic in (1), you need to post the message using ::PostThreadMessage() to put it in the thread's message queue.

All of this is rather well illustrated by the fact the ::PostQuitMessage(status) is a shorthand for

::PostThreadMessage(::GetCurrentThreadId(), WM_QUIT, status, 0);

EDIT:

It seems that people have been led into thinking that ::PostThreadMessage(...,WM_QUIT,...); doesn't work because it doesn't get the special treatement of setting the QS_QUIT flag that is set by ::PostQuitMessage(). If that was the case, then there would be no way to send WM_QUIT to another thread's message queue. Here is proof that it works anyways.

In particular, pay attention to the constants Use_PostQuitMessage and GetMessage_UseWindowHandle. Feel free to change the values and play around with the code. It works just as advertised in my answer, except that I mistakenly used ::GetCurrentThread() rather than ::GetCurrentThreadId() before trying it out.

#include <Windows.h>
#include <iomanip>
#include <iostream>

namespace {

        // Doesn't matter if this is 'true' or 'false'.
    const bool Use_PostQuitMessage        = false;

        // Setting this to 'true' prevents the application from closing.
    const bool GetMessage_UseWindowHandle = false;

    void post_quit_message ()
    {
        if ( Use_PostQuitMessage ) {
            ::PostQuitMessage(0);
        }
        else {
            ::PostThreadMessageW(::GetCurrentThreadId(), WM_QUIT, 0, 0);
        }
    }

    ::BOOL get_message ( ::HWND window, ::MSG& message )
    {
        if ( GetMessage_UseWindowHandle ) {
            return (::GetMessageW(&message, window, 0, 0));
        }
        else {
            return (::GetMessageW(&message, 0, 0, 0));
        }
    }

    ::ULONG __stdcall background ( void * )
    {
            // Allocate window in background thread that is to be interrupted.
        ::HWND window = ::CreateWindowW(L"STATIC", 0, WS_OVERLAPPEDWINDOW,
            0, 0, 512, 256, 0, 0, ::GetModuleHandleW(0), 0);
        if ( window == 0 ) {
            std::cerr << "Could not create window." << std::endl;
            return (EXIT_FAILURE);
        }

            // Process messages for this thread's windows.
        ::ShowWindow(window, SW_NORMAL);
        ::MSG message;
        ::BOOL result = FALSE;
        while ((result = get_message(window,message)) > 0)
        {
                // Handle 'CloseWindow()'.
            if ( message.message == WM_CLOSE )
            {
                post_quit_message(); continue;
            }
                // Handling for 'ALT+F4'.
            if ((message.message == WM_SYSCOMMAND) &&
                (message.wParam == SC_CLOSE))
            {
                post_quit_message(); continue;
            }
                // Dispatch message to window procedure.
            ::TranslateMessage(&message);
            ::DispatchMessageW(&message);
        }
            // Check for error in 'GetMessage()'.
        if ( result == -1 )
        {
            std::cout << "GetMessage() failed with error: "
                << ::GetLastError() << "." << std::endl;
            return (EXIT_FAILURE);
        }
        return (EXIT_SUCCESS);
    }

}

int main ( int, char ** )
{
        // Launch window & message pump in background thread.
    ::DWORD id = 0;
    ::HANDLE thread = ::CreateThread(0, 0, &::background, 0, 0, &id);
    if ( thread == INVALID_HANDLE_VALUE ) {
        std::cerr << "Could not launch thread." << std::endl;
        return (EXIT_FAILURE);
    }

        // "do something"...
    ::Sleep(1000);

        // Decide to close application.
    ::PostThreadMessageW(id, WM_QUIT, 0, 0);

        // Wait for everything to shut down.
    ::WaitForSingleObject(thread, INFINITE);

        // Return background thread's success code.
    ::DWORD status = EXIT_FAILURE;
    ::GetExitCodeThread(thread,&status);
    return (status);
}

P.S.:

To actually test the single-threaded use of ::PostThreadMessage(::GetCurrentThreadId(),...); invoke ::background(0); in main instead of launching the thread.

坏尐絯℡ 2024-10-12 07:29:30

只是一个猜测。您的退出代码是从另一个具有自己的消息泵的窗口的消息循环调用中调用的吗?根据 MSDN for WaitForSingleObject,您会无限期地阻止当前 UI 线程并阻止处理其自己的消息。


http://msdn.microsoft.com/en-us /library/ms687032%28VS.85%29.aspx

调用等待时要小心
函数和代码直接或
间接创建窗口。如果一个
线程创建任何窗口,它必须
处理消息。消息广播
被发送到系统中的所有窗口。
使用等待函数的线程
没有超时间隔可能会导致
系统陷入僵局。二
间接的代码示例
创建窗口是 DDE 和
CoInitialize 函数。因此,如果
你有一个线程创建
Windows,使用 MsgWaitForMultipleObjects
或 MsgWaitForMultipleObjectsEx,而是
比 WaitForSingleObject。

可能是 WM_Quit 消息被广播到您自己的窗口,由于您的 WaitForSingleObject 调用,该窗口不处理任何消息。请尝试使用 MsgWaitForMultipleOjbects,它会不时尝试调用您的消息循环。

你的,
阿洛伊斯·克劳斯

Just a guess. Your quit code is called from within a message loop call from another window which has its own message pump? According to MSDN for WaitForSingleObject you block the current UI thread indefinitely and prevent processing its own messages.

From
http://msdn.microsoft.com/en-us/library/ms687032%28VS.85%29.aspx

Use caution when calling the wait
functions and code that directly or
indirectly creates windows. If a
thread creates any windows, it must
process messages. Message broadcasts
are sent to all windows in the system.
A thread that uses a wait function
with no time-out interval may cause
the system to become deadlocked. Two
examples of code that indirectly
creates windows are DDE and the
CoInitialize function. Therefore, if
you have a thread that creates
windows, use MsgWaitForMultipleObjects
or MsgWaitForMultipleObjectsEx, rather
than WaitForSingleObject.

It could be that the WM_Quit message is broadcast to your own window which does not process any messages due to your WaitForSingleObject call. Try instead MsgWaitForMultipleOjbects which tries to call your message loop from time to time.

Yours,
Alois Kraus

计㈡愣 2024-10-12 07:29:30

WM_QUIT 不是窗口消息,因此您不应将其发送到窗口。尝试使用 PostThreadMessage< /a> 相反:

PostThreadMessage(GetThreadId(_hNotifyWindowThread), WM_QUIT, 0, 0);

如果这不起作用,请尝试向您的窗口发布一条虚拟消息:

::PostMessage( _pobjNotifyWindow->m_hWnd, WM_APP, 0, 0 );

并将其用作窗口过程中退出的信号:

case WM_APP:
  PostQuitMessage(0);

WM_QUIT is not a window message, so you shouldn't be sending it to a window. Try using PostThreadMessage instead:

PostThreadMessage(GetThreadId(_hNotifyWindowThread), WM_QUIT, 0, 0);

If that doesn't work, try posting a dummy message to your window:

::PostMessage( _pobjNotifyWindow->m_hWnd, WM_APP, 0, 0 );

and use it as a signal to quit in your window procedure:

case WM_APP:
  PostQuitMessage(0);
只是偏爱你 2024-10-12 07:29:30

您的 GetMessage(...) 仅检索您提供的窗口的消息。然而,WM_QUIT 没有与其关联的窗口。您需要在没有窗口句柄(即 NULL)的情况下调用 GetMessage,以检索消息队列中的任何消息。

Your GetMessage(...) retrieves only message of the window you supplied. However WM_QUIT does not have a window associated with it. You Need to call GetMessage without a window handle (i.e. NULL) which retrieve any message in the Message-Queue.

一桥轻雨一伞开 2024-10-12 07:29:30

详细解释 TheUndeadFish 所说的话;当调用 PostQuitMessage 时,线程消息队列的唤醒标志中设置了一个“秘密”未记录的 QS_QUIT 标志。 GetMessage 以特定顺序查看其唤醒标志,以确定接下来要处理的消息。

如果它发现设置了QS_QUIT,则会生成WM_QUIT 消息并导致GetMessage 返回FALSE。

您可以获取当前使用 获取队列状态

有关详细信息,请参阅 Microsoft Windows 编程应用程序,第 4 版(遗憾的是,最新版本删除了这些主题)。

To elaborate on what TheUndeadFish said; there's a "secret" undocumented QS_QUIT flag set in the wake flags of the thread's message queue when PostQuitMessage is called. GetMessage looks at its wake flags in a particular order to determine what message to process next.

If it finds that QS_QUIT is set, it generates a WM_QUIT message and causes GetMessage to return FALSE.

You can get a thread's documented wake flags that are currently set with GetQueueStatus.

The details can be found in Programming Applications for Microsoft Windows, 4th Edition (unfortunately, the newest edition removed these topics).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文