在阅读本文内容之前,请首先查看下面的代码,猜猜程序的运行效果是下面两种结果中的哪一个?
- 程序运行1秒后,弹出消息框。如果不关闭该消息框,程序将不会有任何变化;直到用户关闭该消息框后,才会弹出后续WM_TIMER对应的消息框;
- 程序运行后,每隔1秒弹出消息框(不管用户是否关闭之前已弹出的消息框)
View Code
1 // Timer.cpp : Defines the entry point for the application.
2 //
3
4 #include "stdafx.h"
5 #include "Timer.h"
6
7 #define MAX_LOADSTRING 100
8
9 // Global Variables:
10 HINSTANCE hInst; // current instance
11 TCHAR szTitle[MAX_LOADSTRING]; // The title bar text
12 TCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
13
14 // Forward declarations of functions included in this code module:
15 ATOM MyRegisterClass(HINSTANCE hInstance);
16 BOOL InitInstance(HINSTANCE, int);
17 LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
18 INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
19
20 int APIENTRY _tWinMain(HINSTANCE hInstance,
21 HINSTANCE hPrevInstance,
22 LPTSTR lpCmdLine,
23 int nCmdShow)
24 {
25 UNREFERENCED_PARAMETER(hPrevInstance);
26 UNREFERENCED_PARAMETER(lpCmdLine);
27
28 // TODO: Place code here.
29 MSG msg;
30 HACCEL hAccelTable;
31
32 // Initialize global strings
33 LoadString(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
34 LoadString(hInstance, IDC_TIMER, szWindowClass, MAX_LOADSTRING);
35 MyRegisterClass(hInstance);
36
37 // Perform application initialization:
38 if (!InitInstance (hInstance, nCmdShow))
39 {
40 return FALSE;
41 }
42
43 hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_TIMER));
44
45 // Main message loop:
46 while (GetMessage(&msg, NULL, 0, 0))
47 {
48 if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
49 {
50 TranslateMessage(&msg);
51 DispatchMessage(&msg);
52 }
53 }
54
55 return (int) msg.wParam;
56 }
57
58
59
60 //
61 // FUNCTION: MyRegisterClass()
62 //
63 // PURPOSE: Registers the window class.
64 //
65 // COMMENTS:
66 //
67 // This function and its usage are only necessary if you want this code
68 // to be compatible with Win32 systems prior to the 'RegisterClassEx'
69 // function that was added to Windows 95. It is important to call this function
70 // so that the application will get 'well formed' small icons associated
71 // with it.
72 //
73 ATOM MyRegisterClass(HINSTANCE hInstance)
74 {
75 WNDCLASSEX wcex;
76
77 wcex.cbSize = sizeof(WNDCLASSEX);
78
79 wcex.style = CS_HREDRAW | CS_VREDRAW;
80 wcex.lpfnWndProc = WndProc;
81 wcex.cbClsExtra = 0;
82 wcex.cbWndExtra = 0;
83 wcex.hInstance = hInstance;
84 wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_TIMER));
85 wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
86 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
87 wcex.lpszMenuName = MAKEINTRESOURCE(IDC_TIMER);
88 wcex.lpszClassName = szWindowClass;
89 wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
90
91 return RegisterClassEx(&wcex);
92 }
93
94 //
95 // FUNCTION: InitInstance(HINSTANCE, int)
96 //
97 // PURPOSE: Saves instance handle and creates main window
98 //
99 // COMMENTS:
100 //
101 // In this function, we save the instance handle in a global variable and
102 // create and display the main program window.
103 //
104 BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
105 {
106 HWND hWnd;
107
108 hInst = hInstance; // Store instance handle in our global variable
109
110 hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
111 CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);
112
113 if (!hWnd)
114 {
115 return FALSE;
116 }
117
118 ShowWindow(hWnd, nCmdShow);
119 UpdateWindow(hWnd);
120
121 return TRUE;
122 }
123
124 //
125 // FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
126 //
127 // PURPOSE: Processes messages for the main window.
128 //
129 // WM_COMMAND - process the application menu
130 // WM_PAINT - Paint the main window
131 // WM_DESTROY - post a quit message and return
132 //
133 //
134 LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
135 {
136 int wmId, wmEvent;
137 PAINTSTRUCT ps;
138 HDC hdc;
139
140 switch (message)
141 {
142 case WM_COMMAND:
143 wmId = LOWORD(wParam);
144 wmEvent = HIWORD(wParam);
145 // Parse the menu selections:
146 switch (wmId)
147 {
148 case IDM_ABOUT:
149 DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
150 break;
151 case IDM_EXIT:
152 DestroyWindow(hWnd);
153 break;
154 default:
155 return DefWindowProc(hWnd, message, wParam, lParam);
156 }
157 break;
158 case WM_PAINT:
159 hdc = BeginPaint(hWnd, &ps);
160 // TODO: Add any drawing code here...
161 EndPaint(hWnd, &ps);
162 break;
163 case WM_DESTROY:
164 PostQuitMessage(0);
165 break;
166 case WM_CREATE:
167 SetTimer(hWnd, 1000, 1000, NULL);
168 break;
169 case WM_TIMER:
170 MessageBox(hWnd, L"Timer Box", L"INFO", MB_OK|MB_ICONINFORMATION);
171 break;
172 default:
173 return DefWindowProc(hWnd, message, wParam, lParam);
174 }
175 return 0;
176 }
177
178 // Message handler for about box.
179 INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
180 {
181 UNREFERENCED_PARAMETER(lParam);
182 switch (message)
183 {
184 case WM_INITDIALOG:
185 return (INT_PTR)TRUE;
186
187 case WM_COMMAND:
188 if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
189 {
190 EndDialog(hDlg, LOWORD(wParam));
191 return (INT_PTR)TRUE;
192 }
193 break;
194 }
195 return (INT_PTR)FALSE;
196 }
注:这是一个向导生成的Win32窗口程序,只需要查看WndProc中的WM_CREATE和WM_TIMER响应代码。
按照我对Windows消息机制的理解,我毫不犹豫的选择了答案1,原因是:
WinMain中的消息循环会被MessageBox函数阻塞住。
只有关闭MessageBox后,消息循环才可能取到后续的WM_TIMER消息,然后弹出后续的消息框。
然而,这段代码实际运行的结果是2,也就是说,不论用户是否关闭之前的消息框,系统都会不断地弹出消息框。
为什么会这样?
第一反应:系统使用不同的线程派发WM_TIMER消息,直接在该线程中调用窗口类对应的窗口过程。修改代码验证一下这个猜测,将MessageBox的标题改为当前线程ID,结果发现所有的消息框都是在同一个线程上下文中被弹出的,这下子颠覆了我对消息循环的理解,完全无法理解这个最简单窗口程序的运行流程。还好有调试器,在OnTimer函数中下断点,断在第二个消息框弹出处。
首先,这个程序只有一个执行线程,那么肯定不存在所谓的WM_TIMER派发线程:
再看看堆栈,上溯调用上下文,发现了关键的信息:第二个MessageBox的调用是从第一个MessageBox中发起的(此图未加载User32.dll的符号,因此不能看到MessageBox符号名)
通过这个调用堆栈,我们可以推断出MessageBox API的实现逻辑:
- 调用CreateWindow创建类别为系统对话框#32770的窗口
- 循环调用GetMessage(&msg, NULL, 0, 0)获取消息,并通过DispatchMessage API将消息派发到对应的窗口过程(注意GetMessage的第二个参数为NULL,这样才可以获取当前线程的所有消息)
通过这样的实现逻辑,才会造成主窗口WndProc的重入,源源不断的处理WM_TIMER消息。
关于MessageBox API的实现细节,肯定不是上面两步那么简单,因为它还需要阻塞一部分发往父窗口的消息。如果有时间,可以考虑使用CreateWindow(而不是DialogBox)来实现一个MessageBox,会对模态/非模态的概念有更清晰的理解。