Closing an app not sending WM_QUIT message? - c++

Having such a simple Win32 app:
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR pCmdLine, int nCmdShow) {
...
HWND hwnd = CreateWindowEx(0, CLASS_NAME, L"Learn to Program Windows", WS_POPUP | WS_BORDER, 0, 0, 190, 110, nHwnd, NULL, hInstance, NULL);
if (hwnd != NULL) {
ShowWindow(hwnd, nCmdShow);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT) {
swprintf_s(msgbuf, _T("WM_QUIT (main)\n"));
OutputDebugString(msgbuf);
}
}
}
return 0;
}
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
swprintf_s(msgbuf, _T("WM_DESTROY\n"));
OutputDebugString(msgbuf);
PostQuitMessage(0);
return 0;
case WM_CLOSE:
swprintf_s(msgbuf, _T("WM_CLOSE\n"));
OutputDebugString(msgbuf);
DestroyWindow(hwnd);
return 0;
case WM_QUIT:
swprintf_s(msgbuf, _T("WM_QUIT\n"));
OutputDebugString(msgbuf);
return 0;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW + 1));
Example_DrawImage9(hdc);
EndPaint(hwnd, &ps);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
When i do Alt+F4 to close it i get WM_CLOSE, WM_DESTROY but NOT WM_QUIT? Why don't I get the WM_QUIT message?

Reading documentation at https://learn.microsoft.com/en-us/windows/win32/winmsg/wm-quit:
The WM_QUIT message is not associated with a window and therefore will
never be received through a window's window procedure. It is retrieved
only by the GetMessage or PeekMessage functions.
Do not post the WM_QUIT message using the PostMessage function; use
PostQuitMessage.
=== Portion after this was added after answer accepted for further clarification ===
As others have noted, there are sort of two things going on in the original code submitted. In the message map, there is a switch case entry for WM_QUIT. The documentation I quoted shows that the message is not for windows and so the case statement will never get processed.
However, there is another issue going on. Look at the message pumping:
MSG msg;
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
if (msg.message == WM_QUIT) {
swprintf_s(msgbuf, _T("WM_QUIT (main)\n"));
OutputDebugString(msgbuf);
}
}
When GetMessage() is called and the WM_QUIT message is the message retrieved in the queue, then the return value from GetMessage() will be zero and so it will immediately exit the while loop.
This is documented at https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmessage
If the function retrieves a message other than WM_QUIT, the return
value is nonzero.
If the function retrieves the WM_QUIT message, the return value is
zero.

Related

How to correctly destroy window that is running from another thread?

The goal that I am trying to achieve is to listen session notifications about lock, unlock and so on. I have to do it in another thread due to architecture and avoiding blocking main thread. Here is what I am doing:
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch (msg)
{
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
case WM_WTSSESSION_CHANGE:
switch (wParam) {
case WTS_CONSOLE_CONNECT:
case WTS_SESSION_LOGON:
case WTS_REMOTE_CONNECT:
case WTS_SESSION_UNLOCK:
case WTS_CONSOLE_DISCONNECT:
case WTS_REMOTE_DISCONNECT:
case WTS_SESSION_LOGOFF:
case WTS_SESSION_LOCK:
case WTS_SESSION_REMOTE_CONTROL:
case WTS_SESSION_CREATE:
case WTS_SESSION_TERMINATE:
break;
default:
break;
}
break;
default:
return DefWindowProc(hwnd, msg, wParam, lParam);
}
return 0;
}
void startListeningNotifications()
{
const wchar_t g_szClassName[] = L"myWindowClass";
WNDCLASSEX wc = {};
wc.lpfnWndProc = WndProc;
wc.lpszClassName = g_szClassName;
wc.cbSize = sizeof(WNDCLASSEX);
if (!RegisterClassEx(&wc))
{
return;
}
HWND hwnd = CreateWindowEx(NULL, g_szClassName, L"", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL, GetModuleHandle(NULL), NULL);
if (hwnd == NULL)
{
return;
}
if (!WTSRegisterSessionNotification(hwnd, NOTIFY_FOR_ALL_SESSIONS))
{
return;
}
ShowWindow(hwnd, SW_HIDE);
MSG Msg = {};
while (GetMessage(&Msg, NULL, 0, 0) > 0)
{
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
}
int main()
{
std::thread listener(startListeningNotifications);
listener.join();
return 0;
}
The problem that I am facing right now is that I do not know how to correctly write stopListeningNotifications() method. I have to somehow destroy my window and quit message loop. Please advice how to do that safely. Thanks in advance.
Provided WndProc function handles WM_CLOSE and WM_DESTROY.
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
So you could send WM_CLOSE message with hwnd of the window created in the thread:
PostMessage(hwnd, WM_CLOSE, 0, 0)

WINAPI postquitmessage leaves window hanging

i'm using a Dialog to ask the user for some input, but my window 'hangs' after the user is done (controls don't respond anymore, but it's still visible) and disappears only when the application quits.
Here's my code:
LRESULT CALLBACK Level2Auth(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {
switch (Message)
{
case WM_CREATE:
{
{... do stuff ...}
CreateWindow(L"BUTTON", L"Connect",
WS_VISIBLE | WS_CHILD | WS_BORDER,
370, 10, 70, 20,
hwnd, (HMENU)1, NULL, NULL);
break;
}
case WM_COMMAND:
{
switch (LOWORD(wParam))
{
case 1:
{
{... retrieve input ...}
Level2Auth(NULL, WM_DESTROY, NULL, NULL);
break;
}
default:
{
break;
}
}
break;
}
case WM_DESTROY:
{
PostQuitMessage(0);
break;
}
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
And the message loop:
INT WINAPI launchLevel2Auth()
{
MSG Msg; HWND hwnd;
hwnd = CreateWindowEx(WS_EX_CLIENTEDGE, L"Level2AuthPopUp", L"Remote PKCS#11 PIN entry", WS_VISIBLE | WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
640,
100,
NULL, NULL, hInstance, NULL);
if (hwnd == NULL) {
return 0;
}
while (GetMessage(&Msg, NULL, 0, 0) > 0) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
My intention was to retrieve some data in a text field as the "Connect" button was clicked, and then to close the window.
Thanks a lot for your answers.
When you are calling your own message handler with WM_DESTROY message directly, you are bypassing any cleanup, that needs to be done by WINAPI framework. Instead, use DestroyWindow:
...
switch (LOWORD(wParam))
{
case 1:
{
{... retrieve input ...}
DestroyWindow (hwnd);
// Level2Auth(NULL, WM_DESTROY, NULL, NULL);
break;
}
...
Article for further reading: Destroying Windows in WINAPI.

WinApi task killing

I have super simple WinApi program. Close button does not destroy process of the program. What should be added?
#include <windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE hINSTANCE;
int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPSTR lpstr,
int nCmdShow) {
// VARIABLES
TCHAR className[] = _T("win32api");
TCHAR windowName[] = _T("Protected Window");
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(NULL, IDI_INFORMATION);
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)COLOR_BACKGROUND;
wcex.lpszMenuName = NULL;
wcex.lpszClassName = className;
wcex.hIconSm = LoadIcon(NULL, IDI_SHIELD);
if (!RegisterClassEx(&wcex)) {
MessageBox(NULL, _T("Cannot register window"), _T("Error"), MB_OK | MB_ICONERROR);
return 0;
}
HWND hWnd = CreateWindow(className,
windowName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
300,
300,
NULL,
NULL,
hInstance,
NULL);
if (!hWnd) {
MessageBox(hWnd, _T("Call to register windows isn't working"), _T("Error"), NULL);
return 1;
}
hINSTANCE = hInstance;
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
MSG msg;
while (GetMessage(&msg, hWnd, 0, 0) != 0 || GetMessage(&msg, hWnd, 0, 0) != -1) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.lParam;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT uInt, WPARAM wParam, LPARAM lParam) {
PAINTSTRUCT ps;
HDC hdc;
switch (uInt) {
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
TextOut(hdc, 10, 20, _T("Text sample right here boyz."), _tcsclen(_T("Text sample right here boyz.")));
EndPaint(hWnd, &ps);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, uInt, wParam, lParam);
break;
}
return 0;
}
If I add one more case
case WM_CLOSE:
PostQuitMessage(0);
break;
It doesnt close at all (pressing close button is not doing anything). Any other tips are very appreciated.
Your message loop is wrong, for two reasons:
you are calling GetMessage() twice. Don't do that! Think of what happens if you do. If the GetMessage() call on the left-hand side of || were to detect WM_QUIT (which it can't, see further below), it would return 0. Which would cause || to call the GetMessage() on the right-hand side, ignoring the WM_QUIT from the previous call and blocking the loop until a new message arrives later. You should call GetMessage() only once per loop iteration, and then act on the return value as needed:
BOOL bRet;
do
{
bRet = GetMessage(&msg, hWnd, 0, 0);
if (bRet == 0) break;
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
while (1);
However, you are also filtering messages by HWND (don't do that!), and so it will only return messages that are posted to the specified HWND via PostMessage(). PostQuitMessage() posts its WM_QUIT message to the input queue of the calling thread itself, not to the HWND. So the filtering will never see the WM_QUIT message. In order for WM_QUIT to break the loop, you need to stop filtering by HWND altogether, or at least make an unfiltered call periodically.
Your message loop should look like this instead. This is the standard message loop:
while (GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
Note in this case, there is no need to handle -1 from GetMessage()!.
To close the app, you need to destroy the window. Typically that's done in one of two ways:
Have your WM_CLOSE handler call DestroyWindow.
Have the DefWindowProc handle the message after you do your thing.
Your WndProc is a little out of the ordinary. I usually see them written as:
{
switch(msg)
{
case WM_CLOSE:
// prompt user, clean stuff up, etc.
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
// Other cases here. Most will fall out of the switch so that
// DefWindowProc can handle them (for other system notifications).
// Only return from the case if you really don't want anybody else
// to handle the message.
}
return DefWindowProcW(hwnd, msg, wParam, lParam);
}
For specifics on WM_CLOSE, see https://msdn.microsoft.com/en-us/library/windows/desktop/ms632617(v=vs.85).aspx, which specifically says that you must call DestroyWindow or let DefWindowProc do it.

Creating two windows in one app

I'm learning C++ and I have some questions. Some researches at msdn didn't help me. I wanna create two windows in one app. One window - dialog with options and another for graphic output. Here is the code that I use. But I have some problems with it:
Second window (for graphic) does not react at close, minimize, iconic buttons presses.
Message Boxes, when appear, are not in focus and not react at any mouse clicks, but in this time main window works fine.
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, int nCmdShow)
{
InitCommonControls();
if(FAILED(DialogWindow_OnCreate(hInstance)))
return 1;
if(FAILED(GraphicWindow_OnCreate(hInstance)))
return 1;
MSG msg;
memset(&msg, 0, sizeof(MSG));
while(msg.message != WM_QUIT)
{
while(PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
OnUpdate();
}
GraphicWindow_OnClose();
return 0;
}
Creation of main window (Dialog):
HRESULT DialogWindow_OnCreate(HINSTANCE hInst)
{
g_hDialogWindow = CreateDialog(hInst, MAKEINTRESOURCE(IDD_MAIN), NULL, DialogWindow_WndProc);
if(!g_hDialogWindow)
return E_NOTIMPL;
UpdateWindow(g_hDialogWindow);
return S_OK;
}
Main window proc (Dialog):
INT_PTR CALLBACK DialogWindow_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
{
g_hDialogWindow = hWnd;
// Some init actions ...
return (INT_PTR)TRUE;
}
case WM_COMMAND:
int wmId, wmEvent;
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
switch(wmEvent)
{
case NULL: // Menu used
{
switch(wmId)
{
case IDC_BTN_1:
{
DialogWindow_OnBtn1();
MessageBoxA(NULL, "Some message", "Some title", MB_OK);
return (INT_PTR)TRUE;
} break;
case IDC_BTN_2:
{
DialogWindow_OnBtn2();
return (INT_PTR)TRUE;
} break;
// ...
case IDCANCEL:
{
// Close window
DefWindowProc(hWnd, WM_CLOSE, wParam, lParam);
} break;
case IDOK:
{
// Close window
DefWindowProc(hWnd, WM_CLOSE, wParam, lParam);
} break;
}
}
}
case WM_CLOSE:
DefWindowProc(hWnd, message, wParam, lParam);
return (INT_PTR)TRUE;
break;
case WM_DESTROY:
PostQuitMessage(0);
return (INT_PTR)TRUE;
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
break;
}
return (INT_PTR)FALSE;
}
Second window (Graphic) creation:
HRESULT GraphicWindow_OnCreate(HINSTANCE hInst)
{
HWND g_hGraphicWindow = NULL;
WNDCLASSEX wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = GraphicWindow_WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInst;
wcex.hIcon = LoadIcon(hInst, MAKEINTRESOURCE(IDI_APP_ICO));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = 0;
wcex.lpszClassName = _T("MyApp");
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
if(!RegisterClassEx(&wcex))
{
DWORD dwError = GetLastError();
if(dwError != ERROR_CLASS_ALREADY_EXISTS)
{
MessageBoxA(NULL, "GraphicWindow: RegisterClass() failed!", "Error", MB_OK | MB_ICONERROR);
return HRESULT_FROM_WIN32(dwError);
}
}
// Set window's initial size, but it might be changed later
int nDefaultWidth = 320;
int nDefaultHeight = 240;
RECT rc;
SetRect(&rc, 0, 0, nDefaultWidth, nDefaultHeight);
AdjustWindowRect(&rc, WS_OVERLAPPEDWINDOW, false);
// Create the window
g_hGraphicWindow = CreateWindowA("MyApp", "GraphicWindow", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT, (rc.right - rc.left), (rc.bottom - rc.top), 0,
0, hInst, 0);
if(!g_hGraphicWindow)
{
DWORD dwError = GetLastError();
MessageBoxA(NULL, "GraphicWindow: CreateWindow() failed!", "Error", MB_OK | MB_ICONERROR);
return HRESULT_FROM_WIN32(dwError);
}
UpdateWindow(g_hGraphicWindow);
return S_OK;
}
Graphic window proc:
LRESULT CALLBACK GraphicWindow_WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_KEYDOWN:
{
switch(wParam)
{
case VK_RETURN:
// Some actions ...
break;
case VK_ESCAPE:
// Some actions ...
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_CLOSE:
ShowWindow(hWnd, SW_HIDE);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
Where is my problem? Help me, please.
I have the follow comments to make:
Do not call DefWindowProc in your dialog procedure. That could well be the main problem. See this example on MSDN: http://msdn.microsoft.com/en-us/library/windows/desktop/ms644996.aspx#modeless_box
Your message loop is somewhat bogus. More on that later.
You should not be calling ANSI functions. Remove all A suffixes. You'll need to prefix string literals with L to specify wide strings. For instance, L"foo".
You should presumably pass the main window handle as hWndParent when you call CreateDialog. Otherwise, the modeless dailog will be unowned and so have its own taskbar button, and not appear always on top of the main window.
You should pass the main window handle as hWnd when you call MessageBox.
Looking in more detail at the message loop, you are not following the rules laid out in the documentation to CreateDialog, which states:
To support keyboard navigation and other dialog box functionality, the message loop for the dialog box must call the IsDialogMessage function.
So your message loop should be:
while(GetMessage(&msg, NULL, 0, 0))
{
if(!IsDialogMessage(g_hDialogWindow, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Note also that I switched to a GetMessage based loop. I don't think you need to run a hot loop for your needs. Using GetMessage allows your app's main thread to yield the CPU and block if it is idle.
In your Dialog Procedure, don't use DefWindowProc.
See: Dialog Box Default Message Processing.

Creating a Window in WinAPI after pressing a button

I'm making an auto clicker for a game in WinAPI and I have 4 simple buttons on the main window. When the user presses the 'start' button I want another window to open asking them for settings such as number of times to click and time between clicks. When I try to create a new window, nothing is happening, but everything else works perfectly.
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
{
PostQuitMessage(0);
return 0;
}
case WM_COMMAND:
{
switch (wParam)
{
case ID_START:
{
HINSTANCE hInstance = GetModuleHandle(CLASS_NAME);
HWND settings = CreateWindowEx(
0,
L"Settings",
L"Settings",
WS_OVERLAPPEDWINDOW | WS_VISIBLE | WS_CHILD,
100, 100, 600, 200,
NULL,
(HMENU) ID_SETTINGS,
hInstance,
NULL
);
MSG msg = { };
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
case ID_QUIT:
{
PostQuitMessage(0);
return 0;
}
case ID_CALIB:
{
if (MessageBox(hwnd, L"You pressed Calibrate", L"Calibrate", MB_OK))
{
return 0;
}
}
case ID_INFO:
{
if (MessageBox(hwnd, L"You pressed about", L"About", MB_OK))
{
return 0;
}
}
}
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH)(COLOR_WINDOW+1));
EndPaint(hwnd, &ps);
return 0;
}
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
I just started WinAPI today, so I am extremely new. Thanks for any help in advance!
The second parameter to CreateWindowEx must be a class name that you registered earlier by calling RegisterClass.
You are specifying WS_CHILD. But a child must have a parent. Pass the parent HWND into the hwndParent parameter.