Author: Eugene Mayevski
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:
1 project XXX;
2 {... }
3 var
4 DM: TMyDataModule;
5
6 begin
7 DM := TMyDataModule.Create;
8 {... }
9 Application.Run;
10 DM.Free;
11 end;
12
13 procedure TMyDataModule.DataModuleDestroy(Sender: TObject);
14 var
15 I: Integer;
16 J: integer;
17 begin
18 for i := 0 to 5 do
19 begin
20 MessageBeep(MB_ICONQUESTION);
21 if MessageBox(0, PChar('Datamodule destroying - ' + IntToStr(i)), nil,
22 MB_SYSTEMMODAL) = 0 then
23 begin
24 j := GetLastError;
25 MessageBeep(MB_ICONEXCLAMATION);
26 MessageBox(0, PChar('MessageBox error - ' + IntToStr(j)), nil,
27 MB_SYSTEMMODAL);
28 end;
29 end;
30 MessageBeep(MB_ICONEXCLAMATION);
31 MessageBox(0, 'Datamodule destroyed', nil, MB_SYSTEMMODAL);
32 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:
33 if Msg.Msg = WM_QUERYENDSESSION then
34 begin
35 Synchronize(CloseApp);
36 WaitForSingleObject(StopWatcherEvent, INFINITE);
37 ResetEvent(StopWatcherEvent);
38 Msg.Result := 1;
39 end
40 else
41 { ... }
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.
42
43 {====================================================}
44 { }
45 { EldoS Visual Components }
46 { }
47 { Copyright (c) 1998-2000, EldoS }
48 { }
49 {====================================================}
50
51 unit ElShutdownWatcher;
52
53 interface
54
55 implementation
56
57 uses
58 Forms, Classes, Windows, Messages, SysUtils;
59
60 type
61 TShutdownThread = class(TThread)
62 private
63 Wnd: HWND;
64 procedure WndProc(var Msg: TMessage);
65 procedure CloseApp;
66 protected
67 procedure Execute; override;
68 end;
69
70 var
71 StopWatcherEvent: THandle;
72
73 procedure TShutdownThread.CloseApp;
74 begin
75 if (Application.MainForm <> nil) and (not Application.Terminated) then
76 Application.MainForm.Close
77 else
78 PostMessage(Application.Handle, WM_QUIT, 0, 0);
79 end;
80
81 procedure TShutdownThread.WndProc(var Msg: TMessage);
82 begin
83 if Msg.Msg = WM_QUERYENDSESSION then
84 begin
85 Synchronize(CloseApp);
86 WaitForSingleObject(StopWatcherEvent, INFINITE);
87 ResetEvent(StopWatcherEvent);
88 Msg.Result := 1;
89 end
90 else
91 DefWindowProc(Wnd, Msg.Msg, Msg.wParam, msg.lParam);
92 end;
93
94 procedure TShutdownThread.Execute;
95 var
96 Msg: TMsg;
97 i: LongBool;
98 begin
99 StopWatcherEvent := CreateEvent(nil, true, false, nil);
100 Wnd := AllocateHWND(WndProc);
101 repeat
102 i := GetMessage(Msg, 0, 0, 0);
103 if i = TRUE then
104 begin
105 TranslateMessage(Msg);
106 DispatchMessage(Msg);
107 if WaitForSingleObject(StopWatcherEvent, 0) = WAIT_OBJECT_0 then
108 break;
109 end;
110 until i <> TRUE;
111 DeallocateHWND(Wnd);
112 CloseHandle(StopWatcherEvent);
113 StopWatcherEvent := 0;
114 end;
115
116 var
117 Watcher: TShutdownThread;
118
119 initialization
120
121 Watcher := TShutdownThread.Create(true);
122 Watcher.FreeOnTerminate := true;
123 Watcher.Resume;
124
125 finalization
126 if StopWatcherEvent <> 0 then
127 SetEvent(StopWatcherEvent);
128
129 end.
|