How to handle WM_ENDSESSION in a console app - c++

I wrote a program to register mouse events, and I want it to terminate when the computer is shutdown (and then perform a flush and a final print).
I tried with a CtrlHandler, but it works only with Ctrl-C and not when the system is shutdown, because I am using a Win32 library, according to MSDN:
If a console application loads the gdi32.dll or user32.dll library, the HandlerRoutine function that you specify when you call SetConsoleCtrlHandler does not get called for the CTRL_LOGOFF_EVENT and CTRL_SHUTDOWN_EVENT events. The operating system recognizes processes that load gdi32.dll or user32.dll as Windows applications rather than console applications. This behavior also occurs for console applications that do not call functions in gdi32.dll or user32.dll directly, but do call functions such as Shell functions that do in turn call functions in gdi32.dll or user32.dll.
To receive events when a user signs out or the device shuts down in these circumstances, create a hidden window in your console application, and then handle the WM_QUERYENDSESSION and WM_ENDSESSION window messages that the hidden window receives. You can create a hidden window by calling the CreateWindowEx method with the dwExStyle parameter set to 0.
So, first I have to create a hidden window, and then I have to intercept the the WM_ENDSESSION message. But how?
I tried to read some examples, but I can't figure out how to do this.
Here is my code:
BOOL WINAPI CtrlHandler(DWORD fdwCtrlType)
{
switch (fdwCtrlType)
{
// Handle the CTRL-C signal.
/*case CTRL_C_EVENT:
printf("Ctrl-C event\n\n");
Beep(750, 300);
return FALSE; //TRUE
// CTRL-CLOSE: confirm that the user wants to exit.
case CTRL_CLOSE_EVENT:
Beep(600, 200);
printf("Ctrl-Close event\n\n");
return FALSE; //TRUE
// Pass other signals to the next handler.
case CTRL_BREAK_EVENT:
Beep(900, 200);
printf("Ctrl-Break event\n\n");
return FALSE;
*/case CTRL_LOGOFF_EVENT:
Beep(1000, 200);
printf("Ctrl-Logoff event\n\n");
myfile << "totale :" << tot;
myfile.flush();
myfile.close();
return TRUE; //FALSE
case CTRL_SHUTDOWN_EVENT:
Beep(750, 500);
printf("Ctrl-Shutdown event\n\n");
myfile << "totale :" << tot;
myfile.flush();
myfile.close();
return TRUE; //FALSE
default:
return FALSE;
}
}
int main(){
if (SetConsoleCtrlHandler(CtrlHandler, TRUE))
{
printf("\nThe Control Handler is installed.\n");
for(;;)
{
code that print the mouse event(........)
}
}
else
{
printf("\nERROR: Could not set control handler");
return 1;
}
return 0;
}

You can just create a hidden GUI window in console application and handle WM_ENDSESSION in the window procedure as shown below
#include <Windows.h>
HWND g_hidden_window = nullptr;
LRESULT CALLBACK wnd_proc(HWND, UINT, WPARAM, LPARAM);
// Main entry point of your app
int main() {
HMODULE current_instance = ::GetModuleHandle(L"");
// Register the window class
WNDCLASSEX window_class_ex = { 0 };
window_class_ex.cbSize = sizeof(WNDCLASSEX);
window_class_ex.lpfnWndProc = wnd_proc;
window_class_ex.lpszClassName = L"Foo";
window_class_ex.hInstance = current_instance;
if (!::RegisterClassEx(&window_class_ex)) {
return 1;
}
// Create an overlapped window
g_hidden_window = ::CreateWindow(
L"Foo",
L"",
WS_OVERLAPPED,
0, 0, 0, 0,
nullptr,
nullptr,
current_instance,
0);
if (!g_hidden_window) {
return 1;
}
MSG message;
// Main message loop
while (::GetMessage(&message, nullptr, 0, 0)) {
::DispatchMessage(&message);
}
}
Now, in your main window procedure, you should handle WM_ENDSESSION. In your case, I see no reason to handle WM_QUERYENDSESSION. You should also handle WM_CLOSE and/or WM_DESTROY to quit the main message loop:
// Main window procedure
LRESULT CALLBACK wnd_proc(HWND window_handle, UINT window_message, WPARAM wparam, LPARAM lparam) {
switch (window_message) {
case WM_ENDSESSION:
if(wparam) {
// According to MSDN this value will be 1 when the system is about to shut down: https://learn.microsoft.com/en-us/windows/win32/shutdown/wm-endsession
// Invoke your function here
CtrlHandler(CTRL_SHUTDOWN_EVENT);
}
break;
case WM_CLOSE:
DestroyWindow(window_handle);
break;
case WM_DESTROY:
::PostQuitMessage(0);
break;
default:
return ::DefWindowProc(window_handle, window_message, wparam, lparam);
}
return 0;
}
To gracefully shut down the app, you will have to break that message loop. To do so, you will have to send a WM_CLOSE message:
SendMessage(g_hidden_window, WM_CLOSE, 0, 0);
Or, explicitly destroy the window by calling:
DestroyWindow(g_hidden_window);
Let me know if it works. I have not tested it because I'm on a Mac right now, but it should work.

Related

PeekMessage always returns FALSE

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);
}
}
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;
}
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.

Deadlock in multi-threaded Windows GUI application

I develop a DAW application for Windows 10. It's a x64 application written in C++ and built by Visual Studio 2019.
The application uses a custom GUI that does not use any Windows APIs but it also has to load VST 2.4 plugins that do use standard Win32 GUI and I open them in modeless popup (non-child) windows.
The problem I've been trying to solve is a deadlock -- see below.
Disclaimer: I know the code isn't perfect and optimized -- it's a work in progress please.
======== main.cpp =============================
// ...
void winProcMsgRelay ()
{
MSG msg;
CLEAR_STRUCT (msg);
while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage (&msg);
DispatchMessage (&msg);
};
}
// ...
int CALLBACK WinMain (HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdL, int nCmdShw)
{
// ...
}
=================================================
1) The WinMain function creates a new thread that will handle our custom GUI (which does not use any Windows API).
2) The WinMain thread uses the standard Windows GUI API and it handles all window messages delivered to our main application window.
The WinMain thread creates our main window by calling CreateWindowEx (with a WNDPROC window procedure callback):
{
WNDCLASSEX wc;
window_menu = CreateMenu ();
if (!window_menu)
{
// Handle error
// ...
}
wc.cbSize = sizeof (wc);
wc.style = CS_BYTEALIGNCLIENT | CS_HREDRAW | CS_VREDRAW;
wc.lpfnWndProc = mainWndProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInst;
wc.hIcon = LoadIcon (NULL, IDI_APP);
wc.hCursor = NULL;
wc.hbrBackground = NULL;
wc.lpszMenuName = mainWinName;
wc.lpszClassName = mainWinName;
wc.hIconSm = LoadIcon (NULL, IDI_APP);
RegisterClassEx (&wc);
mainHwnd = CreateWindowEx (WS_EX_APPWINDOW | WS_EX_OVERLAPPEDWINDOW | WS_EX_CONTEXTHELP,
mainWinName, mainWinTitle,
WS_OVERLAPPEDWINDOW | WS_VISIBLE,
CW_USEDEFAULT, 0,
0, 0,
NULL, NULL, hInst, NULL);
// ...
// Then the WinMain thread keeps executing a standard window message processing loop
// ...
while (PeekMessage (&msg, NULL, 0, 0, PM_NOREMOVE) != 0
&& ! requestQuit)
{
if (GetMessage (&msg, NULL, 0, 0) == 0)
{
requestQuit = true;
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (! requestQuit)
{
WaitMessage ();
}
}
// ...
}
3) Our custom-GUI thread (spawned above), in addition to its other functions, does the following:
a) Loads a VST audio plugin from a DLL file by calling LoadLibrary.
b) Creates a new thread for the DLL plugin (let's call it "plugin thread") to create a new instance of it (there may be multiple instances of a loaded DLL plugin):
vst_instance_thread_handle = (HANDLE) _beginthreadex (NULL, _stack_size, redirect, (void *) this, 0, NULL);
c) After some time that the plugin instance has been running on its own thread, our custom-GUI thread (in response to a user action in our custom GUI) creates a new thread for the plugin GUI window:
vst_gui_thread_handle = (HANDLE) _beginthreadex (NULL, _stack_size, redirect, (void *) this, 0, NULL);
(Note that the DLL plugin uses standard Win32 GUI.)
When the new plugin GUI thread is being spawned, the function VSTGUI_open_vst_gui is called on the plugin instance thread -- see below:
============ vst_gui.cpp: ====================
// ...
struct VSTGUI_DLGTEMPLATE: DLGTEMPLATE
{
WORD e[3];
VSTGUI_DLGTEMPLATE ()
{
memset (this, 0, sizeof (*this));
};
};
static INT_PTR CALLBACK VSTGUI_editor_proc_callback (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
thread_local AEffect * volatile Vst_instance_ptr = 0;
thread_local volatile int Vst_instance_index = -1;
thread_local volatile UINT_PTR Vst_timer_id_ptr = 0;
thread_local volatile HWND Vst_gui_handle = NULL;
void VSTGUI_open_vst_gui (int vst_instance_index)
{
AEffect *vst_instance = VST_instances [vst_instance_index].vst->pEffect;
Vst_instance_index = vst_instance_index;
Vst_instance_ptr = vst_instance;
VSTGUI_DLGTEMPLATE t;
t.style = WS_POPUPWINDOW | WS_MINIMIZEBOX | WS_DLGFRAME | WS_VISIBLE |
DS_MODALFRAME | DS_CENTER;
t.cx = 100; // We will set an appropriate size later
t.cy = 100;
VST_instances [vst_instance_index].vst_gui_open_flag = false;
Vst_gui_handle = CreateDialogIndirectParam (GetModuleHandle (0), &t, 0, (DLGPROC) VSTGUI_editor_proc_callback, (LPARAM) vst_instance);
if (Vst_gui_handle == NULL)
{
// Handle error
// ...
}
else
{
// Wait for the window to actually open and initialize -- that will set the vst_gui_open_flag to true
while (!VST_instances [vst_instance_index].vst_gui_open_flag)
{
winProcMsgRelay ();
Sleep (1);
}
// Loop here processing window messages (if any), because otherwise (1) VST GUI window would freeze and (2) the GUI thread would immediately terminate.
while (VST_instances [vst_instance_index].vst_gui_open_flag)
{
winProcMsgRelay ();
Sleep (1);
}
}
// The VST GUI thread is about to terminate here -- let's clean up after ourselves
// ...
return;
}
// The plugin GUI window messages are handled by this function:
INT_PTR CALLBACK VSTGUI_editor_proc_callback (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
AEffect* vst_instance = Vst_instance_ptr;
int instance_index = Vst_instance_index;
if (VST_instances [instance_index].vst_gui_window_handle == (HWND) INVALID_HANDLE_VALUE)
{
VST_instances [instance_index].vst_gui_window_handle = hwnd;
}
switch(msg)
{
case WM_INITDIALOG:
{
SetWindowText (hwnd, String (tmp_str) + VST_get_best_vst_name (instance_index, false));
if (vst_instance)
{
ERect* eRect = 0;
vst_instance->dispatcher (vst_instance, effEditGetRect, 0, 0, &eRect, 0);
if (eRect)
{
// ...
SetWindowPos (hwnd, HWND_TOP, x, y, width, height, SWP_SHOWWINDOW);
}
vst_instance->dispatcher (vst_instance, effEditOpen, 0, 0, hwnd, 0);
}
}
VST_instances [instance_index].vst_gui_open_flag = true;
if (SetTimer (hwnd, (UINT_PTR) Vst_instance_ptr, 1, 0) == 0)
{
logf ("Error: Could not obtain a timer object for external VST GUI editor window.\n");
}
return 1;
case WM_PAINT:
{
PAINTSTRUCT ps;
BeginPaint (hwnd, &ps);
EndPaint (hwnd, &ps);
}
return 0;
case WM_MOVE:
if (Vst_instance_index >= 0)
{
VST_instances [Vst_instance_index].vst_gui_win_pos_x = VST_get_vst_gui_win_pos_x (Vst_instance_index);
VST_instances [Vst_instance_index].vst_gui_win_pos_y = VST_get_vst_gui_win_pos_y (Vst_instance_index);
}
return 0;
case WM_SIZE:
if (Vst_instance_index >= 0)
{
VST_instances [Vst_instance_index].vst_gui_win_width = VST_get_vst_gui_win_width (Vst_instance_index);
VST_instances [Vst_instance_index].vst_gui_win_height = VST_get_vst_gui_win_height (Vst_instance_index);
}
return 0;
case WM_TIMER:
if (vst_instance != NULL)
{
vst_instance->dispatcher (vst_instance, effEditIdle, 0, 0, 0, 0);
}
return 0;
case WM_CLOSE:
// ...
return 0;
case WM_NCCALCSIZE:
return 0;
default:
return (DefWindowProc (hwnd, msg, wParam, lParam));
}
return 0;
=================================================
Our custom-GUI thread, too, periodically calls winProcMsgRelay (); Sleep (1); in a loop.
Why multi-threaded? Because: 1) this is a real-time audio-processing application where near-zero latencies are required, and 2) we need to set CPU priorities and stack sizes independently for each thread, based on their real needs. Also, 3) having multi-threaded GUI allows our DAW app to remain responsive when the plugin or its GUI becomes unresponsive and 4) we make us of multi-core CPUs.
Everything is working well. I can open multiple instances of multiple plugins. Their GUI windows can even spawn other windows showing progress bars, all that without any deadlock.
However, the problem is that I get a deadlock when I click the app logo in a plugin GUI window (Absynth 5 and Kontakt 6 by Native Instruments), which apparently creates a child modal window, which, by the way, displays correctly and fully.
But both this modal window and the parent GUI window stop responding to user actions and window messages -- they "hang" (our custom GUI keeps working well, though). The same thing happens when the plugin GUI displays a standard Windows modal MessageBox on error, where the MessageBox is completely "frozen".
When I set a debugger breakpoint in VSTGUI_open_vst_gui in the second loop that calls winProcMsgRelay, I can determine that this is the place where it hangs, because when I get the deadlock state, that breakpoint is never triggered.
I know that modal dialogs have their own message loop that might block ours, but how should I redesign my code to accommodate for that?
I also know that SendMessage and the like are blocking until they get response. That's why I use the asynchronous PostMessage, instead.
I confirmed that the deadlock occurs in 32-bit builds of the application, too.
I've been trying to trace the cause for several weeks. I believe I've done all my homework and I honestly don't know what else to try. Any help would be greatly appreciated.
There is a lot of code not appearing here (e.g. winProcMsgRelay) and I will admit I'm finding it difficult to get a mental picture of how this works, but let me offer you some general advice and some things to keep in mind.
First of all, modal dialogs have their own message loop. As long as they are up, your message loop will not run.
Second of all, windows functions like SetWindowPos SetWindowText actually send a message to the window. Are you calling those from the thread that that created the window? Because if not, that means that the calling thread will block while the OS sends the message to the window and waits for a response. If the thread that created those windows is busy, the sending thread will remain blocked until it is not.
If I were attempting to debug this, I would simply wait until it deadlocks, then break into the debugger and bring up the threads and call stacks windows next to each other. Switch context among the threads in the threads windows (double click on them) and look at the resulting thread call stacks. You should be able to spot the problem.
Ok, I was able to resolve the deadlock myself. The solution was to rewrite the code so as to unify the window proc handlers (VST GUI messages are handled by the same callback function as the main window messages). Moreover, unlike the official VST SDK, which uses DialogBoxIndirectParam to create the plugin window, I now use CreateWindowEx, instead (not sure if this contributed to solving the deadlock issue, though). Thanks for the comments.

Why doesn't my Windows API callback function run? [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 4 years ago.
Improve this question
I've written a code that terminates egui.exe. But my callback function doesn't run after the WM_CLOSE message is processed. Why?
#include <iostream>
#include <windows.h>
#include <psapi.h>
using namespace std;
HWND ESETWindow = 0; //Variable to store a handle to the main window
// of ESET Smart Security
HWND ConfirmationWindow = 0; //Variable to store a handle to the
// confirmation dialog box that appears
// when a WM_CLOSE message is sent to
// the ESET window.
DWORD EGUIPID = 0; //Variable to store the process identifier of egui.exe
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam);
VOID SendAsyncProc(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult);
int main()
{
cout << "Terminating ESET Smart Security..." << endl;
//Obtain an array of process identifiers including the PID of egui.exe
DWORD PIDs[1024]; //The array of process identifiers
DWORD bytesReturned;
if (!EnumProcesses(PIDs, sizeof(PIDs), &bytesReturned))
{
cerr << "Unable to enumerate the processes." << endl;
return 0;
}
//Enumerate the PIDs array to find the process of egui.exe.
DWORD nProcesses = bytesReturned / sizeof(DWORD);
for (DWORD i = 0;i < nProcesses;i++)
{
//Open the process to examine its executable file name.
HANDLE process = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, PIDs[i]);
//Create a buffer for the name of the executable file.
PSTR processName = (PSTR) VirtualAlloc((LPVOID) NULL, (DWORD) 10, MEM_COMMIT, PAGE_READWRITE);
//Get the name of the executable file name.
GetModuleBaseName(process, NULL, processName, 10);
//Check if the executable file name is egui.exe.
if (!lstrcmpi(processName, TEXT("egui.exe")))
{
EGUIPID = PIDs[i];
break;
}
VirtualFree(processName, 0, MEM_RELEASE);
CloseHandle(process);
}
//Display an error message if the process identifier of egui.exe
//could not be found.
if (!EGUIPID)
{
cerr << "Unable to find process identifier of egui.exe" << endl;
return 0;
}
//Enumerate the top-level windows to find the main window of
// ESET Smart Security and store a handle to that window in the
// ESETWindow variable.
EnumWindows(EnumWindowsProc, 0);
//Display an error message if the window could not be found.
if (!ESETWindow)
{
cerr << "Unable to find the primary window of ESET Smart Security." << endl;
return 0;
}
//Send a WM_CLOSE message to the main window of egui.exe in order
// to display a confirmation dialog box.
if (!SendMessageCallback(ESETWindow, WM_CLOSE, 0, 0, SendAsyncProc, 0))
{
cerr << "Unable to send a WM_CLOSE message to the primary window of ESET Smart Security." << endl;
return 0;
}
//Wait a second for the confirmation dialog box to appear...
Sleep(1000);
//Enumerate the windows again to find the confirmation dialog box.
EnumWindows(EnumWindowsProc, 1);
//Display an error message if the confirmation dialog box
// could not be found.
if (!ConfirmationWindow)
{
cerr << "Unable to find confirmation message." << endl;
cout << "If you have ESET NOD32, it is probably terminated successfully.";
cin.get();
}
//Find the Yes button in the confirmation dialog box and display
// an error message if failed.
HWND button = FindWindowEx(ConfirmationWindow, NULL, NULL, TEXT("&Yes"));
if (!button)
{
cerr << "Unable to find Yes button in the message box." << endl;
return 0;
}
//Activate the confirmation dialog box and simulate a mouse click
// on the Yes button.
SetActiveWindow(ConfirmationWindow);
SendMessage(button, BM_CLICK, 0, 0);
cout << "ESET Smart Security was successfully terminated!";
//Keep the program running until the user presses Enter
// in the console window.
cin.get();
return 0;
}
//If lParam is 0, the function below finds the main window of
//ESET Smart Security, otherwise, it finds the confirmation
//dialog box.
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
{
//Check if hwnd belongs to a window of egui.exe.
DWORD PID = 0;
GetWindowThreadProcessId(hwnd, &PID);
if (PID != EGUIPID)
return TRUE; //Exit function and continue the enumeration.
//Check if the title of the window is "ESET Smart Security"
// or "ESET Node32 Antivirus".
int len = GetWindowTextLength(hwnd);
PSTR title = (PSTR) VirtualAlloc((LPVOID) NULL, (DWORD) (len + 1), MEM_COMMIT, PAGE_READWRITE);
GetWindowText(hwnd, title, (len + 1));
if ((lstrcmp(title, TEXT("ESET Smart Security"))) && (lstrcmp(title, TEXT("ESET NOD32 Antivirus"))))
return TRUE; //Exit function and continue the enumeration.
if (lParam)
{
//If lParam is nonzero and hwnd refers to the main
// window of egui.exe, exit function and continue
// the enumeration.
if (hwnd == ESETWindow)
return TRUE;
//Otherwise hwnd refers to the confirmation dialog box.
//So store it in the ConfirmationWindow variable.
ConfirmationWindow = hwnd;
return FALSE; //Exit function and stop the enumeration.
}
else
{
//hwnd refers to the main window of ESET Smart Security.
//So store it in the ESETWindow variable.
ESETWindow = hwnd;
return FALSE; //Exit function and stop the enumeration.
}
}
VOID SendAsyncProc(HWND hwnd, UINT uMsg, ULONG_PTR dwData, LRESULT lResult)
{
MessageBox(0, TEXT("ESET Smart Security was successfully terminated."), TEXT("Result"), MB_OK);
}
Although this program accomplishes its task perfectly, the callback function SendAsyncProc doesn't run after the WM_CLOSE message is processed and egui.exe is terminated. Could you tell me why?
The documentation for SendMessageCallback tells you, why your callback will not ever be called:
If the target window belongs to a different thread from the caller, then the callback function is called only when the thread that called SendMessageCallback also calls GetMessage, PeekMessage, or WaitMessage.
The target window obviously belongs to a different thread, because it runs in a different process. Your code doesn't perform any message retrieval. No callback.

Handling Windows messages so that my application responds correctly

I had an old piece of code that I wrote about 15 years ago to do some file manipulation, which runs through a script to process 1/2 sourcefiles and output to 1/2 outputfiles and I've been trying to write it 'properly' so that it will run under Windows 7/8 as a scheduled task.
I've ported it to Visual Studio 2013 Express, and managed to get it to work (execute and generate the desired results) pretty quickly, processing my text input file, stripping rubbish out of it, and generating a formatted CSV file in about 38 seconds, however it Ghosts, and goes non-responsive until such time as the processing of the file completes, which is okish for user execution, but Windows 7 and 8 don't like running it as a scheduled task, and close it as soon as it ghosts.
I've tried re-writing the large chunk of processing code so that it returns to the main message handling loop after each line of script, and this functions, albeit about ten-twenty times slower (depending on whether I'm using GetMessage or PeekMessage, however I'm still struggling with the application ghosting, despite each line of script only taking a few milliseconds to run.
My Main Window code is currently;
while (msg.message != WM_QUIT)
{
while ((PeekMessage(&msg, NULL, 0, WM_COMMAND - 1, PM_REMOVE) > 0) ||
(PeekMessage(&msg, NULL, WM_COMMAND+1, 0xFFFF, PM_REMOVE) > 0))
// While there are any system messages with a value <> WM_COMMAND,
// Process these first
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0)
// Process one WM_COMMAND message i.e. one of mine.
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
where my WndProc function has a giant case statement that processes all my posted messages to perform one sub-task of my file processing and all the windows system-type responses. I've cut out some of the 'doing stuff' commands, but you'll get the gist
it's a;
until we reach my iteration bit in the scriptfile;
process phase 1
until we finish processing the sourcefile or go round enough iterations;
process phase 2
until we reach the end of the sourcefile
process phase 3
end
switch (message)
{
case WM_COMMAND:
wmId = LOWORD(wParam);
wmEvent = HIWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
//////////////////////////////
/* Start of my menu uptions */
//////////////////////////////
case MI_FILE_EXIT:
DestroyWindow(hWnd);
return(0);
case MI_RUN_EXECUTE:
// * Open Necessary Files * //
// * Start Processing ScriptFile * //
phase = 1;
command = 'A';
PostMessage(hWnd, WM_COMMAND, MI_PHASE_1, lParam);
return(0);
case MI_PHASE_1:
nextcommand = ProcessCommand(command);
if (ScriptFileComplete)
{
// We've reached the end of the script - stop processing
PostMessage(hWnd, WM_COMMAND, MI_SCRIPT_COMPLETE, lParam);
break;
}
else if (nextcommand = '<')
{
// prep for start of phase 2
phase = 2;
tiptr = tptr;
command = nextcommand;
PostMessage(hWnd, WM_COMMAND, MI_PHASE_2, lParam);
break;
}
else
{
// process the nextcommand
command = nextcommand;
PostMessage(hWnd, WM_COMMAND, MI_PHASE_1, lParam);
break;
}
return(0);
case MI_PHASE_2:
nextcommand = ProcessCommand(command);
redrawscreen(hWnd);
if (nextcommand == '>')
{
// we're at the end of the iteration
if (UseIterationCount)
{
IterationCount--;
if (IterationCount <= 0)
{
phase = 3;
command = nextcommand;
PostMessage(hWnd, WM_COMMAND, MI_PHASE_3, lParam);
break;
}
}
}
else if (ScriptFileComplete)
{
// We've reached the end of the script - stop processing
PostMessage(hWnd, WM_COMMAND, MI_SCRIPT_COMPLETE, lParam);
break;
}
else if (SourceFileAComplete)
// We've reached the end of the Source File - stop processing
{
PostMessage(hWnd, WM_COMMAND, MI_SCRIPT_COMPLETE, lParam);
break;
}
else // All's normal and we're just processing the next command
{
command = nextcommand;
PostMessage(hWnd, WM_COMMAND, MI_PHASE_2, lParam);
break;
}
return(0);
case MI_PHASE_3:
nextcommand = ProcessCommand(command);
command = nextcommand;
// test to see if we go round phase 3 loop again
if ((!ScriptFileComplete) && (!SourceFileAComplete))
{
PostMessage(hWnd, WM_COMMAND, MI_PHASE_3, lParam);
break;
}
else // we've cleared that and we're at scriptfile EOF
{
PostMessage(hWnd, WM_COMMAND, MI_SCRIPT_COMPLETE, lParam);
break;
}
return(0);
case MI_SCRIPT_COMPLETE:
// We're at the end of the script - close things down.
CloseFiles();
DrawMenuBar(hWnd);
if (AutoExit)
PostMessage(hWnd, WM_QUIT, wParam, lParam);
return(0);
case MI_DO_NOTHING: // blank code for initial entry to main message loop
return(0);
default: // somehow an invalid message was posted
log("invalid message was posted");
return(0);
}
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code here...
redrawscreen(hWnd);
EndPaint(hWnd, &ps);
return(0);
case WM_DESTROY:
PostQuitMessage(0);
return(0);
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return(0);
}
So the idea is that after each call to processCommand (i.e. carry out one line of my script processing), return to the main message handle, respond to any events (like someone moving the window or clicking on the close button), and then process the next line of my script.
I'm clearly missing something (or lots of things) regarding which messages I should be checking and processing on a regular basis, so if anyone can offer suggestions/advice as to;
What messages should I be handling before all others?
How often should my application be checking for system messages?
Am I going about this completely wrong?
e.g. should I be threading the processing code? - the app shouldn't require any user interaction while it's running, but it would be good practise to allow a user to move/resize/quit it, which I was hoping to achieve through posting messages back to WM_COMMAND
Thank you very much for even reading this far. Any and all advice no matter how sharp (if it helps) appreciated.
Richard.
- UPDATE -
Thanks both for the pointers - it's clear that this does warrant threading my slow file access bits of code, so I've re-written to do this, but now I'm just a bit stuck as to where I should do fire off the thread / join afterwards - There's lots of examples where the thread is fired off in the main window and then joined straight afterwards e.g.;
void My_Slow_Task(){
; // Process lots of data
}
int main(){
std::thread t1(My_Slow_Task);
t1.join();
return 0;
}
but as pointed out below doing this in my message handling block just means it sits there like a lemon until the thread finishes, but I need a user to be able to make changes to the outputfiles etc. before they kick off the file manipulation process (it can be automated, but it needs to be able to not be as well), so at the moment, I'm after something like;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
// one of these messages is the menuitem for 'Run_my_big_chunk_of_processing' and sets 'ExecutingScript' to TRUE
// another will be to change the location of the output file or edit the script etc.
if (ExecutingScript == TRUE)
{
ExecutingScript = FALSE;
std::thread t1(Run_my_big_chunk_of_processing);
t1.join();
PostMessage(hWnd, WM_COMMAND, MI_RUN_COMPLETE, lParam);
// to do the close files, tidy up and feed back to the user.
}
}
If anyone's knows how I can handle the request from the user to start the slow process and fire it off, without the GUI having to wait for it to finish before handling other messages, then that'll be it sorted.
- SOLUTION? -
Use an outer control loop for user interaction while the thread isn't running, and an inner one for while it is;
_twinMain()
{
// Initialise Stuff
// Pre-exec message loop
// Outer messagehandling loop
while (!AllDone)
// we're !AllDone on entry, and AllDone when the app gets
// a WM_QUIT or other triggered abort e.g. script failure
{
// Pre/post-exec message loop
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
if (ExecutingScript)
// this will be set by auto-run in the ini file,
// or manually by a user clicking the menu option
{
std::thread t1(RunThread);
// Exec message loop
while (ExecutingScript)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) > 0)
// There may be no messages, as we could be in auto-run
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
t1.join();
PostMessage(hWnd, WM_COMMAND, MI_RUN_COMPLETE, lParam);
}
}
return (int) msg.wParam;
}
Thanks for all your help
VS2013 has the C++11 <thread> stuff, no need for non-standard libraries. std::aync(std::launch::async) will run a background task, std::future::wait_for allows you to check from the messsage loop whether the operation has finished. Of course, you can also use a old-style PostQuitMessage(WM_APP) from the processing thread. Either way, after the message loops exits, call .join() on the worked thread to clean it up, then exit the main thread.

Closing Window and Thread Generates Invalid Window Handle Error - C++

My console application spawns a new (invisible) window in its own thread. Prior to exiting the application, it attempts to clean up, and the last call to GetMessage in the window's message pump fails. GetLastError returns 1400, "Invalid window handle."
This is how the clean up proceeds in the application thread:
if ( s_hNotifyWindowThread != NULL )
{
ASSERT(s_pobjNotifyWindow != NULL);
::PostMessage( s_pobjNotifyWindow->m_hWnd, WM_CLOSE, 0, 0 );
::WaitForSingleObject( s_hNotifyWindowThread, 50000L ); // Step 1: breakpoint here
::CloseHandle( s_hNotifyWindowThread ); // Step 4: breakpoint here
s_hNotifyWindowThread = NULL;
}
This WndProc exists in the new thread created for the window:
static LRESULT CALLBACK WndProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
switch ( uMsg )
{
case WM_CLOSE:
::DestroyWindow( hWnd ); // Step 2: breakpoint here
break;
case WM_DESTROY:
::PostQuitMessage( 0 ); // Step 3: breakpoint here
break;
case WM_DEVICECHANGE:
/* Handle device change. */
break;
default:
// Do nothing.
break;
}
return ::DefWindowProc( hWnd, uMsg, wParam, lParam );
}
This is in the window's thread function where the new window is created and my message pump is located:
s_pobjNotifyWindow = new CNotifyWindow( pParam );
while ( (bRetVal = ::GetMessage(
&msg, // message structure
s_pobjNotifyWindow->m_hWnd, // handle to window whose messages are to be retrieved
0, // lowest message value to retrieve
0 // 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(); // Step 5: breakpoint here: Returns error 1400
break;
default: // Other message received.
::TranslateMessage( &msg );
::DispatchMessage( &msg );
break;
}
}
What am I doing wrong? Thanks.
You are passing an invalid window handle to GetMessage, which is why it's failing and reporting that a window handle is invalid.
You'll see the same error if you run this code with a made-up window handle:
MSG msg = {0};
BOOL b = ::GetMessage(&msg, (HWND)(0x123000), 0, 0);
if (b == -1)
{
DWORD dwErr = ::GetLastError();
wprintf(L"%lu\n", dwErr);
}
The problem is that you are still using the window handle after the window has been destroyed. As soon as a window is destroyed its handle is invalid (or, worse, re-used by some other window). The message pump won't exit until it processes the quit-message. Since the quit-message is posted during window destruction it will be processed after window destruction is complete. i.e. Your message pump keeps running for a short time after your window is destroyed.
Just pass NULL to GetMessage for the window argument, so that it retrieves messages for all windows on that thread. Since the thread only exists for that one window it's only going to get messages for that window anyway. (Plus the quit-message posted to the thread itself, and potentially messages for other windows which things like COM creates if you use COM on that thread... You'd definitely want to process those messages, so telling GetMessage to filter by window is doing nothing at best and could prevent something from working at worst, in addition to the error you're seeing GetMessage return.)