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.