闻过则喜

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

在阅读本文内容之前,请首先查看下面的代码,猜猜程序的运行效果是下面两种结果中的哪一个?

  1. 程序运行1秒后,弹出消息框。如果不关闭该消息框,程序将不会有任何变化;直到用户关闭该消息框后,才会弹出后续WM_TIMER对应的消息框;
  2. 程序运行后,每隔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的实现逻辑:
  1. 调用CreateWindow创建类别为系统对话框#32770的窗口
  2. 循环调用GetMessage(&msg, NULL, 0, 0)获取消息,并通过DispatchMessage API将消息派发到对应的窗口过程(注意GetMessage的第二个参数为NULL,这样才可以获取当前线程的所有消息)
通过这样的实现逻辑,才会造成主窗口WndProc的重入,源源不断的处理WM_TIMER消息。
 
关于MessageBox API的实现细节,肯定不是上面两步那么简单,因为它还需要阻塞一部分发往父窗口的消息。如果有时间,可以考虑使用CreateWindow(而不是DialogBox)来实现一个MessageBox,会对模态/非模态的概念有更清晰的理解。
posted on 2013-03-08 16:07  闻过则喜  阅读(1694)  评论(0编辑  收藏  举报