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;