How to handle WM_ENDSESSION in a console app

Viewed 609

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;
}
1 Answers

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://docs.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