Home > Software engineering >  PeekMessage always returns FALSE
PeekMessage always returns FALSE

Time:07-27

I have written a small test application that inserts files (with hardcoded paths) into the currently active folder/application via delayed rendering. It works as expected. But I have a question - why does PeekMessage always return FALSE? But if you remove the PeekMessage call, Wndproc will never be called. I read a similar post, but I'm creating a window in the same thread in which I'm trying to process messages.

Code:

static LRESULT CALLBACK WindProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam) {
    switch (Msg) {      
        case WM_RENDERALLFORMATS: {
            OpenClipboard(hWnd);
            EmptyClipboard(); 
        }
        case WM_RENDERFORMAT: {
            printf("WM_RENDERFORMAT received");
            
            <Here the file paths are copied to the clipboard>

            if (Msg == WM_RENDERALLFORMATS)
                CloseClipboard();
            return 0;
        }
        case WM_DESTROYCLIPBOARD:
            return 0;
    }
    return DefWindowProc(hWnd, Msg, wParam, lParam);
}

HWND hwnd_;

void thread_(void* ignored) {
    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof(WNDCLASSEX);
    wcx.lpfnWndProc = WindProc;
    wcx.hInstance = GetModuleHandle(NULL);
    wcx.lpszClassName = TEXT("my_class");
    RegisterClassEx(&wcx);

    hwnd_ = CreateWindowEx(0, TEXT("my_class"), TEXT(""), 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);

    MSG msg;
    while (true) {
        if (PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) {
            printf("PeekMessage returned TRUE\n");
            TranslateMessage(&msg);
            DispatchMessage(&msg);
            break;
        }
        Sleep(1000);
    }
}

void main() {
    CloseHandle((HANDLE)_beginthread(thread_, 0, NULL));

    // let's give some time to thread to create msg window
    Sleep(100);

    if (OpenClipboard(hwnd_)) {
        EmptyClipboard();
        SetClipboardData(CF_HDROP, NULL);
        CloseClipboard();
    }

    while (true) {
        Sleep(100);
    }
}

CodePudding user response:

why does PeekMessage always return FALSE?

Assuming that the window handle that you pass to PeekMessage is valid, then the reason for PeekMessage returning FALSE is simply that there are no messages in the queue.

That this is the case can be discerned from the documentation which says:

If a message is available, the return value is nonzero.

If no messages are available, the return value is zero.

CodePudding user response:

PeekMessage() (and GetMessage()) only returns messages that are posted via PostMessage() or PostThreadMessage() to the calling thread's message queue. PeekMessage() returns FALSE when there is no posted message available at the moment it is called. GetMessage() blocks until a posted message becomes available.

However, the messages in question (WM_RENDERFORMAT and WM_RENDERALLFORMATS) are instead being sent via SendMessage...() directly to the target window. But, the messages are being sent by another thread, so PeekMessage() (or GetMessage()) is still needed, as they internally dispatch messages that are sent across thread boundaries.

This is stated in PeekMessage()'s documentation:

Dispatches incoming nonqueued messages, checks the thread message queue for a posted message, and retrieves the message (if any exist).

...

During this call, the system dispatches (DispatchMessage) pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function. Then the first queued message that matches the specified filter is retrieved. The system may also process internal events. If no filter is specified, messages are processed in the following order:

  • Sent messages
  • Posted messages
  • Input (hardware) messages and system internal events
  • Sent messages (again)
  • WM_PAINT messages
  • WM_TIMER messages

As well as the GetMessage() documentation:

Retrieves a message from the calling thread's message queue. The function dispatches incoming sent messages until a posted message is available for retrieval.

...

During this call, the system delivers pending, nonqueued messages, that is, messages sent to windows owned by the calling thread using the SendMessage, SendMessageCallback, SendMessageTimeout, or SendNotifyMessage function. Then the first queued message that matches the specified filter is retrieved. The system may also process internal events. If no filter is specified, messages are processed in the following order:

  • Sent messages
  • Posted messages
  • Input (hardware) messages and system internal events
  • Sent messages (again)
  • WM_PAINT messages
  • WM_TIMER messages

And SendMessage()'s documentation:

If the specified window was created by the calling thread, the window procedure is called immediately as a subroutine. If the specified window was created by a different thread, the system switches to that thread and calls the appropriate window procedure. Messages sent between threads are processed only when the receiving thread executes message retrieval code. The sending thread is blocked until the receiving thread processes the message. However, the sending thread will process incoming nonqueued messages while waiting for its message to be processed. To prevent this, use SendMessageTimeout with SMTO_BLOCK set. For more information on nonqueued messages, see Nonqueued Messages.


With that said, your handling of the WM_RENDERALLFORMATS message is wrong. For one thing, it must not call EmptyClipboard(), and for another, it is not checking whether your app is still the clipboard owner before rendering its data. See What is the proper handling of WM_RENDERFORMAT and WM_RENDERALLFORMATS? for why these points are important.

Also, you have a race condition in your main thread. Before it calls OpenClipboard(), it sleeps for only 100ms to wait for the window to be created first, but there is no guarantee that hwnd_ will have been assigned within that 100ms. It could take that long just for the worker thread to start running, for instance.

A better option is to have the worker thread signal an event when the window is created, and then have the main thread wait on that event (even better would be to simply move the initial SetClipboardData() call into the worker thread itself after creating the window, but you have dismissed that option).

Try something more like this instead:

static LRESULT CALLBACK WindProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
    switch (uMsg) {      

        /*
        case WM_CREATE:
            if (OpenClipboard(hWnd)) {
                EmptyClipboard();
                SetClipboardData(CF_HDROP, NULL);
                CloseClipboard();
            }
            return 0;
        }
        */

        case WM_RENDERALLFORMATS: {
            if (OpenClipboard(hWnd)) {
                if (GetClipboardOwner() == hWnd) {
                    SendMessage(hWnd, WM_RENDERFORMAT, CF_HDROP, 0);
                }
                CloseClipboard();
            }
            return 0;
        }

        case WM_RENDERFORMAT: {
            printf("WM_RENDERFORMAT received");            
            if (wParam == CF_HDROP) {
                // <Here the file paths are copied to the clipboard>
            }
            return 0;
        }

        case WM_DESTROYCLIPBOARD:
            return 0;
    }

    return DefWindowProc(hWnd, Msg, wParam, lParam);
}

HWND hwnd_ = NULL;

void thread_(void* arg) {
    HANDLE hEvent = (HANDLE)arg;

    WNDCLASSEX wcx = { 0 };
    wcx.cbSize = sizeof(WNDCLASSEX);
    wcx.lpfnWndProc = WindProc;
    wcx.hInstance = GetModuleHandle(NULL);
    wcx.lpszClassName = TEXT("my_class");
    RegisterClassEx(&wcx);

    hwnd_ = CreateWindowEx(0, TEXT("my_class"), TEXT(""), 0, 0, 0, 0, 0, HWND_MESSAGE, NULL, GetModuleHandle(NULL), NULL);

    SetEvent(hEvent);

    if (hwnd_ == NULL) {
        return 0;
    }

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0)) {
        printf("GetMessage returned a message\n");
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

int main() {
    HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!hEvent)
        return 1;

    uintptr_t res = _beginthread(thread_, 0, hEvent);
    if (res == -1L) {
        CloseHandle(hEvent);
        return 1;
    }

    WaitForSingleObject(hEvent, INFINITE);
    CloseHandle(hEvent);

    if (hwnd_ != NULL) {
        if (OpenClipboard(hwnd_)) {
            EmptyClipboard();
            SetClipboardData(CF_HDROP, NULL);
            CloseClipboard();
        }
    }

    HANDLE hThread = (HANDLE)res;
    WaitForSingleObject(hThread, INIFINTE);
    CloseHandle(hThread);

    return 0;
}
  • Related