I have an application whose job is to start and stop various other processes.
The problem is that Qt applications don't stop cleanly. The Qt window closes, but the process still runs in the background, until TerminateProcess() is called and then the Qt app exits without cleaning up.
I'm using this method as outlined by Microsoft. Even the Qt source uses that method for terminating processes, except they also post a WM_CLOSE to the main thread. I've added that in to my application too but it still just closes the Window, leaving the process.
What I find interesting is that if I use Windows Task Manager to "End Task" (Not "End Process"), the window closes and the process ends too, so I know it's possible. If I use spy++ I can see that the main window and the main thread both receive WM_CLOSE messages from both Task Manager and my application, but only by using Task Manager do the messages continue on to WM_DESTROY, WM_NCDESTROY etc and end up with the process ending. This problem only happens with Qt applications. Win32 / MFC etc applications terminate cleanly using my application.
How are you supposed to cleanly close Qt apps (assume Qt app source not available)?
--------Edit--------
Here's some sample code that will reproduce the problem. At the very least, I'd be interested to know if other people see the same problem I'm seeing.
The sample code starts CMake (download here), but any Qt app should do.
#include <Windows.h>
#include <iostream>
BOOL CALLBACK TerminateAppEnum(HWND hwnd, LPARAM pid);
int _tmain(int argc, _TCHAR* argv[])
{
char* processName = "C:\\Program Files (x86)\\CMake\\bin\\cmake-gui.exe";
//char* processName = "C:\\Windows\\Notepad.exe";
std::cout << "Creating process \"" << processName << "\"" << std::endl;
STARTUPINFO si = {0};
si.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION pi = {0};
BOOL success = CreateProcess(processName,
"",
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi);
if (success)
{
std::cout << "Press any key to cleanly terminate process..." << std::endl;
std::cin.get();
std::cout << "Cleanly terminating process..." << std::endl;
EnumWindows(TerminateAppEnum, (LPARAM)pi.dwProcessId);
PostThreadMessage(pi.dwThreadId, WM_CLOSE, 0, 0);
if (WaitForSingleObject(pi.hProcess, 10000) == WAIT_OBJECT_0)
{
std::cout << "Success! The process has terminated" << std::endl;
}
else
{
std::cout << "Failed! The process is still running" << std::endl;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
else
{
std::cout << "Unable to start process (Error " << GetLastError() << ")" << std::endl;
}
std::cout << "Press any key to exit..." << std::endl;
std::cin.get();
return 0;
}
BOOL CALLBACK TerminateAppEnum(HWND hwnd, LPARAM pid)
{
DWORD dwPID;
GetWindowThreadProcessId(hwnd, &dwPID);
if (dwPID == (DWORD)pid)
{
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
return TRUE;
}
In Windows right click the taskbar, select Task Manager, on the Process tab, find the process by Name.. End task.
While stopping a process using the Task Manager will most likely stabilize your computer, ending a process can completely close an application or crash your computer, and you could lose any unsaved data.
Okay, solved it.
The problem comes from the fact that Qt creates a Top-Level window - a QEventDispatcher
window. Following the procedure outlined by Microsoft, this window gets a WM_CLOSE message, which shuts down that window and its thread. After that, when the application's main window is closed, no clean up is done and the process remains in system memory.
What's interesting is that by using Task Manager, the QEventDispatcher doesn't get a WM_CLOSE message so stays active, and so when the main window ges a WM_CLOSE message the process exits cleanly. I can only assume that a call to something like IsWindowVisible is being used in the EnumWindowsProc callback in Task Manager, contrary to their documentation. Although that documentation was last reviewed over a decade ago!
Adding in the call to IsWindowVisible is making the program work with all of the Qt apps, and the other non-Qt apps seem to be happy continuing to work with this change too. For completeness, I've included the updated sample code:
#include <Windows.h>
#include <iostream>
BOOL CALLBACK TerminateAppEnum(HWND hwnd, LPARAM pid);
int _tmain(int argc, _TCHAR* argv[])
{
char* processName = "C:\\Program Files (x86)\\CMake\\bin\\cmake-gui.exe";
//char* processName = "C:\\Windows\\Notepad.exe";
std::cout << "Creating process \"" << processName << "\"" << std::endl;
STARTUPINFO si = {0};
si.cb = sizeof(STARTUPINFO);
PROCESS_INFORMATION pi = {0};
BOOL success = CreateProcess(processName,
"",
NULL,
NULL,
FALSE,
0,
NULL,
NULL,
&si,
&pi);
if (success)
{
std::cout << "Press any key to cleanly terminate process..." << std::endl;
std::cin.get();
std::cout << "Cleanly terminating process..." << std::endl;
EnumWindows(TerminateAppEnum, (LPARAM)pi.dwProcessId);
if (WaitForSingleObject(pi.hProcess, 10000) == WAIT_OBJECT_0)
{
std::cout << "Success! The process has terminated" << std::endl;
}
else
{
std::cout << "Failed! The process is still running" << std::endl;
}
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
else
{
std::cout << "Unable to start process (Error " << GetLastError() << ")" << std::endl;
}
std::cout << "Press any key to exit..." << std::endl;
std::cin.get();
return 0;
}
BOOL CALLBACK TerminateAppEnum(HWND hwnd, LPARAM pid)
{
DWORD dwPID;
GetWindowThreadProcessId(hwnd, &dwPID);
if (dwPID == (DWORD)pid)
{
if (IsWindowVisible(hwnd))
{
PostMessage(hwnd, WM_CLOSE, 0, 0);
}
}
return TRUE;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With