Why Is My App Freezing?—An Introduction to Message Loops & Threads
This phenomenon is not isolated to any particular message. It's a fundamental property of the Windows message loop: when one message is being processed, no other message can be processed at the same time. It's not exactly implemented this way, but you can think of it as a queue, where your app pulls the messages out of the queue to process in the reverse order that they are inserted.
Therefore, spending too long processing any message is going to suspend the processing of other messages, effectively freezing your application (because it cannot process any input). The only way to solve this problem is the obvious one: don't spend too long processing any one message.
Often that will mean delegating the processing to a background thread. You will still need to handle all messages on the main thread, and the background worker threads need to report back to the main method when they are finished. All interaction with the GUI needs to happen on a single thread, and that is almost always the main thread in your application (which is why it is often called the UI thread).
(And to answer an objection raised in your question, yes, you can operate multiple threads on single processor machines. You won't necessarily see any performance improvements, but it will make the UI more responsive. The logic here is that a thread can only do one thing at a time, but a processor can switch between threads extremely rapidly, effectively simulating doing more than one thing at a time.)
More useful information is available here in this MSDN article: Preventing Hangs in Windows Applications
Special Cases: Modal Event Processing Loops
Certain window operations on Windows are modal operations. Modal is a common word in computing that basically refers to locking the user into a particular mode where they cannot do anything else until they change (i.e. get out of that) modes. Whenever a modal operation is begun, a separate new message processing loop is spun up and message handling happens there (instead of your main message loop) for the duration of the mode. Common examples of these modal operations are drag-and-drop, window resizing, and message boxes.
Considering the example here of window resizing, your window receives a WM_NCLBUTTONDOWN
message, which you pass to DefWindowProc
for default processing. DefWindowProc
figures out that the user intends to start a move or resize operation, and entered a moving/sizing message loop located somewhere deep in the bowels of Windows' own code. Thus, your application's message loop is no longer running because you've entered into a new moving/sizing mode.
Windows runs this moving/sizing loop as long as the user is interactively moving/sizing the window. It does this so that it can intercept mouse messages and process them accordingly. When the moving/sizing operation completes (e.g., when the user releases the mouse button or presses the Esc key), control will return to your application code.
It is worth pointing out that you are notified that this mode change has occurred via the WM_ENTERSIZEMOVE
message; the corresponding WM_EXITSIZEMOVE
message indicates that the modal event-processing loop has exited. That allows you to create a timer that will continue to generate WM_TIMER
messages that your application can process. The actual details of how this is implemented are relatively unimportant, but the quick explanation is that DefWindowProc
continues to dispatch WM_TIMER
messages to your application inside of its own modal event processing loop. Use the SetTimer
function to create a timer in response to the WM_ENTERSIZEMOVE
message, and the KillTimer
function to destroy it in response to the WM_EXITSIZEMOVE
message.
I only point that out for completeness, though. In the majority of Windows apps that I've written, I've never needed to do that.
So, What Is Wrong With My Code?
Aside from all of that, the behavior you describe in the question are unusual. If you create a new, blank Win32 application using the Visual Studio template, I doubt you will be able to replicate this behavior. Without seeing the rest of your window procedure, I can't tell if you're blocking on any messages (as discussed above), but the part I can see in the question is wrong. You must always call DefWindowProc
for messages that you do not explicitly process yourself.
In this case, you might be fooled into thinking that you're doing that, but WM_SYSCOMMAND
can have lots of different values for its wParam
. You only handle one of those, SC_CLOSE
. All of the rest of them just get ignored because you return 0
. That includes all of the window moving and resizing functionality (e.g. SC_MOVE
, SC_SIZE
, SC_MINIMIZE
, SC_RESTORE
, SC_MAXIMIZE
, etc. etc.).
And there's really no good reason to handle WM_SYSCOMMAND
yourself; just let DefWindowProc
take care of it for you. The only time you need to handle WM_SYSCOMMAND
is when you've added custom items to the window menu, and even then, you should pass every command that you do not recognize on to DefWindowProc
.
A basic window procedure should look like this:
LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch(uMsg)
{
case WM_CLOSE:
DestroyWindow(hWnd);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
It is also possible that your message loop is wrong. The idiomatic Win32 message loop (located near the bottom of your WinMain
function) looks like this:
BOOL ret;
MSG msg;
while ((ret = GetMessage(&msg, nullptr, 0, 0)) != 0)
{
if (ret != -1)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
// An error occurred! Handle it and bail out.
MessageBox(nullptr, L"Unexpected Error", nullptr, MB_OK | MB_ICONERROR);
return 1;
}
}
You do not need hooks of any kind. The MSDN documentation on these is very good, but you're right: they're complicated. Stay away until you have a better understanding of the Win32 programming model. It is a rare case indeed where you need the functionality provided by a hook.