Windows程序设计学习笔记(1)--Windows的消息机制
导言
众所周知,windows是一个消息驱动的系统,它使用两种方式把各种事件通知给应用程序。一种是把消息放在应用程序的消息队列中,另一种是向适当的窗口过程直接发送消息。本文先简单介绍Windows消息机制的一些基本概念,然后以Petzold经典著作《Windows程序设计》中的HELLOWIN程序为例,介绍Windows程序的基本工作过程。由于是初学者,理解有误之处望高手不吝赐教,多谢!
基本概念
1. 窗口类和窗口过程
Windows程序中的每个窗口都是基于一个窗口类来创建的,而基于一个窗口类可以创建多个窗口。窗口类为与其相关联的窗口定义了窗口过程和一些特性。窗口类数据结构定义如下:
typedef struct _WNDCLASS {
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPCTSTR lpszMenuName;
LPCTSTR lpszClassName;
} WNDCLASS, *PWNDCLASS;
其中最重要的是第二个和最后一个字段,即窗口过程和窗口类的名称。
窗口过程(win prock)是窗口的"消息中心",Windows程序中的一切消息最终都是调用相应的win prock进行处理的。win prock可以是应用程序的某个函数,也可以位于一个动态链接库(dll)中。一个Windows程序可以包含多个窗口过程,而一个窗口过程总是与一个窗口类相关联。窗口过程定义如下(窗口过程的名称可任意命名,只要不冲突即可):
LRESULT CALLBACK WndProc(HWND hwnd , UINT message , WPARAM wParam , LPARAM lParam)
2.消息与消息队列
消息是Windows传递给窗口过程的一个结构体,其结构定义如下:
typedef struct tagMSG {
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG, *PMSG;
其中最重要的是hwnd和message对象。hwnd表示接收消息的窗口的句柄,message是消息标识符。windows中用消息标识符表示当前消息是什么消息。消息标识符以WM(window message)为前缀,如WM_PAINT消息 。
Windows程序开始执行时,首先为该程序创建一个“消息队列”(message queue)。对于有多个执行线程的Windows程序,每个线程的消息队列仅为那些其窗口过程在该线程内执行的窗口进行消息处理。(这句话我不太理解,请高手赐教。是每个线程都有一个消息队列吗?)
3.队列消息和非队列消息
Windows消息分为队列消息和非队列消息,队列消息是指被“投递”(post)到消息队列里的windows消息;而非队列消息是windows函数直接“发送”(send)给窗口的消息。需要说明的是,这里的“发送给窗口”就是指Windows调用窗口的窗口过程。
队列消息主要由用户的输入产生,主要形式为按键消息、由按键产生的字符消息(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)、鼠标单击(WM_LBUTTONDOWN)等。此外,队列消息还包括定时器消息(WM_TIMER)、重绘消息(WM_PAINT)和退出消息(WM_QUIT)。
非队列消息包括队列消息以外的所有消息。非队列消息通常是调用Windows函数引起的。如CreateWindow函数引起的WM_CREATE消息 、ShowWindow函数引起的WM_SIZE消息和WM_SHOWWINDOW消息 、DestroyWindow函数引起的WM_DESTROY消息。还需要注意的是,键盘或鼠标输入的队列消息也能够产生非队列消息。
示例HELLOWIN程序
#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT ("HelloWin") ;
HWND hwnd ;
MSG msg ;
WNDCLASS wndclass ;
wndclass.style = CS_HREDRAW | CS_VREDRAW ;
wndclass.lpfnWndProc = WndProc ;
wndclass.cbClsExtra = 0 ;
wndclass.cbWndExtra = 0 ;
wndclass.hInstance = hInstance ;
wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ;
wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ;
wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
wndclass.lpszMenuName = NULL ;
wndclass.lpszClassName = szAppName ;
if (!RegisterClass (&wndclass))
{
MessageBox (NULL, TEXT ("This program requires Windows NT!"),
szAppName, MB_ICONERROR) ;
return 0 ;
}
hwnd = CreateWindow (szAppName, // window class name
TEXT ("The Hello Program"), // window caption
WS_OVERLAPPEDWINDOW, // window style
CW_USEDEFAULT, // initial x position
CW_USEDEFAULT, // initial y position
CW_USEDEFAULT, // initial x size
CW_USEDEFAULT, // initial y size
NULL, // parent window handle
NULL, // window menu handle
hInstance, // program instance handle
NULL) ; // creation parameters
ShowWindow (hwnd, iCmdShow) ;
UpdateWindow (hwnd) ;
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc ;
PAINTSTRUCT ps ;
RECT rect ;
switch (message)
{
case WM_CREATE:
PlaySound (TEXT ("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
GetClientRect (hwnd, &rect) ;
DrawText (hdc, TEXT ("Hello, Windows 98!"), -1, &rect,
DT_SINGLELINE | DT_CENTER | DT_VCENTER) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
HelloWin程序的执行步骤大致为:初始化窗口类对象->注册窗口类对象->创建窗口->显示和更新窗口->进入消息循环(Message Loop)。
1.初始化窗口类
上文已经介绍窗口类的结构定义。在HelloWin程序中,wndclass的第二个字段设置为WndProc函数,也就是说基于wndclass所创建的窗口的窗口过程都是wndclass函数。最后一个字段设置为"HelloWin" ,也就是说wndclass的名称为"HelloWin",这个名字在创建窗口时起到了关联窗口和窗口类的作用。其他的一些字段用以设置wndclass的类风格(class style),窗口的标题栏图标,鼠标指针,背景画刷等。
2.注册窗口类
调用RegisterClass函数注册wndclass 。此函数只有一个参数,即一个指向窗口类的指针。注册成功则创建窗口,否则报错并退出程序。
3.创建窗口
CreateWindow函数用以创建窗口,一个窗口总是基于一个窗口类创建的,CreateWindow的第一个参数就是关联的窗口类的名称。我们要创建的窗口正是通过这种方式和窗口类wndclass建立了关联。CreateWindow函数返回一个窗口的句柄hwnd。
4.显示和更新窗口
ShowWindow函数用以显示窗口,此函数有两个参数,一个数要显示的窗口的句柄hwnd ,另一个参数iCmdShow用以指定窗口在屏幕上的初始显示形式,即是正常显示还是最大化或最小化显示。另外,ShowWindow函数会将WM_SIZE消息和WM_SHOWWINDOW消息发送给窗口消息(这里就是调用WndProc 函数)。
然后调用UpdateWindow函数,此函数向窗口过程(即WndProc函数)发送一个WM_PAINT消息,从而使客户区重绘。(此处WM_PAINT函数是队列消息还是非队列消息呢?)
5.进入消息循环
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
首先用GetMessage函数从消息队列中获取一个消息,此函数有四个参数,第一个参数是一个MSG结构体的指针。第二个到第四个参数这里都设为NULL或0,表示希望获取由该程序所创建的所有窗口的消息。循环体内的TranslateMessage函数将msg结构体返还给Windows以进行某些键盘消息的转换。然后DispatchMessage函数根据msg结构中的hwnd字段把这条消息发送给合适的窗口过程进行处理。
6.消息的处理
HelloWin程序中的所有消息,最终都是由WndProc函数进行处理的。此函数使用一个分支结构对不同的消息进行处理。HelloWin程序只处理了WM_CREATE消息、WM_PAINT消息和WM_DESTROY消息。对于其他消息,均调用DefWindowProc 函数进行处理。我们可以发现,在WinMain函数中没有直接调用WndProc函数,而是在初始化窗口类wndclass时将WndProc函数赋值给了wndclass的lpfnWndProc字段。
窗口过程几乎都是由Windows自身调用的。有两种方式调用窗口过程,一种是消息循环中的DispancheMessage将消息分发给合适的窗口过程;另外一种是Windows的API函数直接将消息发送给窗口过程。如果应用程序希望调用自身的窗口过程,则可以通过函数SendMessage来实现。
posted on 2011-01-18 23:01 Green Billow 阅读(853) 评论(0) 收藏 举报
浙公网安备 33010602011771号