7

I have a console application which uses SetConsoleCtrlHandler to set a handler which handles and CTRL_CLOSE_EVENT. The handler simply returns TRUE which will cause a dialog box to appear and prompt the user to continue shutdown or cancel.

The software runs on Windows XP SP3 and Windows Web Server 2008 SP2.

On XP, when the 'X' on the console window is clicked, my control handler gets called and a prompt appears as expected. On Server 2008 closing the console window does not call my control handler and the application closes down without prompting.

To check that the control handler is being set correctly I have added a case for CTRL_C_EVENT. I can see the code get called for Ctrl-C.

Are there any differences in the way close events are handled in Server 2008? It seems like they do not go through the ctrl handlers at all.

EDIT: Looking at the MSDN page for SetConsoleCtrlHandler I can't find any information about CTRL_CLOSE_EVENT no longer being handled in Vista and later.

If you are dealing with windows (HWND) instead of console ctrl events, is it possible to get the close messages sent to the console window and handle that?

  • Yeah, this behavior was changed in Vista. Programs can no longer block shutdown. – Hans Passant Nov 25 '10 at 17:16
  • 2
    So is there no more notification at all? Is it possible for processes to perform any clean up when closed like this? – Christopher Howlin Nov 25 '10 at 17:20
  • Yes this has changed in Vista (Server 2008) ; you also don't receive logoff events any more. For my own console application I created hidden window and it receives both close and logoff events. – bronekk Jan 19 '12 at 11:47
  • 1
    Comments here seem suspect. `CTRL_CLOSE_EVENT` is about closing the application - NOT about logging off the session or shutting down the system. Intercepting `CTRL_CLOSE_EVENT` doesn't prevent a force-close prompt appearing even in XP (just checked), so why would Vista and later need to stop reporting that (denying console apps the chance to cleanly shut down - e.g. save files) just to get the same forced close dialog that happened anyway if you take too long? –  Apr 26 '18 at 07:17
  • Relevant information found in answers to [this question](https://stackoverflow.com/q/11959643/180247) –  Apr 26 '18 at 08:11

1 Answers1

4

Here is what I do in my console application (running on Windows 7):

i. Create hidden window to wait for close/logoff notification. Important: give it own thread for its message loop

void interrupt::start()
{
  WNDCLASSEX wc = {};
  HINSTANCE hi = GetModuleHandle(NULL);

  wc.cbSize        = sizeof(WNDCLASSEX);
  wc.lpfnWndProc   = WndProc;
  // . . . etc

  if(!RegisterClassEx(&wc))
    return;

  hwnd_ = CreateWindowEx(WS_EX_CLIENTEDGE, class_name, "Waiting for user logoff event",
    WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, hi , NULL);

  ShowWindow(hwnd_, SW_HIDE);
  UpdateWindow(hwnd_);

  MSG msg = {};
  while(GetMessage(&msg, NULL, 0, 0))
  {
    TranslateMessage(&msg);
    DispatchMessage(&msg);
  }

  // call internal function for sending "stop" notification to rest of program
  ctrl.stop(CTRL_CLOSE_EVENT);
}

ii. Implement handling of "special events" in your window message handler

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  switch(msg)
  {
  case WM_ENDSESSION:
    if (lParam)
    {
      // call internal function for sending "stop" notification to rest of program
      ctrl.stop(lParam == ENDSESSION_LOGOFF ? (int) interrupt::logoff : CTRL_CLOSE_EVENT);
    }
    break;
  case WM_CLOSE:
    DestroyWindow(hwnd);
    break;
  case WM_DESTROY:
    PostQuitMessage(0);
    break;
  default:
    return DefWindowProc(hwnd, msg, wParam, lParam);
  }
  return 0;
}

iii. Implement internal function for handling "stop" requests. It must handle 2 special conditions:

a. when not called from window message thread, send WM_CLOSE to window and wait for its thread to exit

b. when not called from main thread, wait for termination of static variables (at least one).

This is because after CtrlHandler exit, Windows will unconditionally terminate your process without giving your code any chance for cleanup. Static variables are destroyed on main thread, so this wait gives you at least guarantee that int main() has exited. You can capture thread id of main thread in the constructor of a static variable (possibly the same which started "shadow" window).

Here is how I did it in my code:

void interrupt::stop(int signal)
{
  // . . .

  // Set exit signal
  InterlockedExchange(&stage_, 2L);

  // Close shadow window if notification is from elsewhere
  if (hwnd_ && GetCurrentThreadId() != thread_.id())
  {
    PostMessage(hwnd_, WM_CLOSE, 0, 0);
    thread_.wait();
  }

  // Wait for completion of own destructor on main thread
  if (GetCurrentThreadId() != main_thread_id_)
    while(stage_ != 3L)
      Sleep(10);
}

// My static variable to wait for
interrupt ctrl;
bronekk
  • 2,051
  • 1
  • 16
  • 18
  • Is this going to be able to intercept closing console window with 'x' button, the thing OP is asking for? – Anonymous Mar 06 '18 at 10:39