Mega Code Archive

 
Categories / Delphi / System
 

Correct handling of Windows shutdown in complex applications

Title: Correct handling of Windows shutdown in complex applications Question: In complex applications it is necessary to correctly process all application finalization steps like OnClose and OnDestroy event handlers for all forms and Data Modules. However after the application has responded to WM_ENDSESSION message (and TApplication does this automatically) lots of API functions fail due to system shutwdown. How to ensure, that all OnDestroy handlers will work correctly? Answer: First let's take a look at the following code: project XXX; ... var DM : TMyDataModule; begin DM := TMyDataModule.Create; ... Application.Run; DM.Free; end; procedure TMyDataModule.DataModuleDestroy(Sender: TObject); var I : Integer; J : integer; begin for i := 0 to 5 do begin MessageBeep(MB_ICONQUESTION); if MessageBox(0, PChar('Datamodule destroying - ' + IntToStr(i)), nil, MB_SYSTEMMODAL) = 0 then begin j := GetLastError; MessageBeep(MB_ICONEXCLAMATION); MessageBox(0, PChar('MessageBox error - ' + IntToStr(j)), nil, MB_SYSTEMMODAL); end; end; MessageBeep(MB_ICONEXCLAMATION); MessageBox(0, 'Datamodule destroyed', nil, MB_SYSTEMMODAL); end; Our goal is to get 7 messageboxes. If you reproduce this code in your application, you will get one message box window, that will immediately disappear. That is not what we want. What should we do? The solution is to not tell windows that the application can be closed until OnDestroy is executed. But if the message is processed in window message dispatching loop, how can we get out of the loop without returning control to Windows? Let's take a look at threads. Windows starts to send WM_ENDSESSION after all windows return 1 in responce to WM_QUERYENDSESSION. And the solution is simple: create a window in another thread and let it process WM_QUERYENDSESSION message in the way, that will shutdown our application correctly. The code in brief is: if Msg.Msg = WM_QUERYENDSESSION then begin Synchronize(CloseApp); WaitForSingleObject(StopWatcherEvent, INFINITE); ResetEvent(StopWatcherEvent); Msg.Result := 1; end else ... CloseApp function calls Application.MainForm.Close. The application is closed. StopWatcherEvent is set only in finalization clause, which is executed after all forms and datamodules are destroyed ;). Here is the complete code of the watcher unit. It has been tested under Windows NT 4.0 SP6. ====================================================================== {====================================================} { } { EldoS Visual Components } { } { Copyright (c) 1998-2000, EldoS } { } {====================================================} unit ElShutdownWatcher; interface implementation uses Forms, Classes, Windows, Messages, SysUtils; type TShutdownThread = class(TThread) private Wnd : HWND; procedure WndProc(var Msg : TMessage); procedure CloseApp; protected procedure Execute; override; end; var StopWatcherEvent : THandle; procedure TShutdownThread.CloseApp; begin if (Application.MainForm nil) and (not Application.Terminated) then Application.MainForm.Close else PostMessage(Application.Handle, WM_QUIT, 0, 0); end; procedure TShutdownThread.WndProc(var Msg : TMessage); begin if Msg.Msg = WM_QUERYENDSESSION then begin Synchronize(CloseApp); WaitForSingleObject(StopWatcherEvent, INFINITE); ResetEvent(StopWatcherEvent); Msg.Result := 1; end else DefWindowProc(Wnd, Msg.Msg, Msg.wParam, msg.lParam); end; procedure TShutdownThread.Execute; var Msg : TMsg; i : LongBool; begin StopWatcherEvent := CreateEvent(nil, true, false, nil); Wnd := AllocateHWND(WndProc); repeat i := GetMessage(Msg, 0, 0, 0); if i = TRUE then begin TranslateMessage(Msg); DispatchMessage(Msg); if WaitForSingleObject(StopWatcherEvent, 0) = WAIT_OBJECT_0 then break; end; until i TRUE; DeallocateHWND(Wnd); CloseHandle(StopWatcherEvent); StopWatcherEvent := 0; end; var Watcher : TShutdownThread; initialization Watcher := TShutdownThread.Create(true); Watcher.FreeOnTerminate := true; Watcher.Resume; finalization if StopWatcherEvent 0 then SetEvent(StopWatcherEvent); end.