windows操作系统最大的特点就是其图形化的操作界面,该界面是建立在消息处理机制的基础上的。windows程序的本质是借助消息来驱动的,程序不断等待,等待任何可能的输入,然后判断,根据不同的消息调用消息处理函数进行适当的处理。这种输入是操作系统捕捉到后以消息形式(一种数据结构)进入到程序中。

消息的基本概念

对于一个win32程序来说,消息机制十分重要,它是一个程序的动力源泉。一个消息是系统定义的一个32位的值,它定义了一个事件,当事件发生时,向windows发出一个通知,告诉应用程序某个事情发生了。例如,单机鼠标,改变窗口尺寸,按下键盘上的一个键都会使windows发送一个消息给应用程序。

windows操作系统中包括以下几种消息:

  1. 标准windows消息。这种消息以WM_开头。
  2. 通知消息。通知消息是针对标准windows控件的消息。这些控件包括:按钮(Button),组合框(ComboBox),编辑框(TextBox)、列表框(ListBox)、ListView控件、Treeview控件、工具条(Toolbar)、菜单(Menu)等。每种消息以不同的字符串打头。
  3. 自定义消息。编程人员还可以自定义消息,这种消息一般在WM_USER基础标识之上增加一个不会冲突的消息标识

消息本身是作为一个记录传递给应用程序的,这个记录中包含了消息的类型以及其他信息。消息的记录类型被定义为MSG,MSG含有来自Windows应用程序消息队列的消息信息,它在Windows中声明如下:

  1. typedef struct tagMsg{
  2. HWND hwnd;
  3. UINT message;
  4. WPARAM wParam;
  5. LPARAM lParam;
  6. DWORD time;
  7. POINT pt;
  8. }MSG

其中:

  • hwnd:该接收消息的窗口句柄
  • message:消息常量标识符
  • wParam:32位消息的特定附加信息,确切含义依赖于消息值
  • lParam:32位消息的特定附加消息,确切含义依赖于消息值
  • time:消息创建时间
  • pt:消息创建时鼠标/光标在屏幕坐标系中的位置

消息可以由消息或应用程序产生,消息产生后,系统会根据消息做出响应。
windows的消息由三个部分组成:

  1. 消息队列,Windows能够为所有的应用程序维护一个消息队列。应用程序必须从消息队列中获取消息,然后分派给某个窗口。
  2. Windows能够为所有的应用程序维护一个消息队列。应用程序必须从消息队列中获取消息,然后分派给某个窗口。
  3. 窗口过程。每个窗口都有一个窗口过程函数来接收传递给窗口的消息,它的任务就是获取消息然后响应它。窗口过程是一个回调函数,处理了一个消息后,它通常要返回一个值给Windows。(注意回调函数是程序中的一种函数,它由Windows或外部模块调用。)

    SDK下的消息机制实现

    为了能够支持消息的实现,需要窗口来提供消息队列以接收特定的消息,需要窗口过程来响应特定的消息,需要消息循环来检索消息。
    一个消息从产生到被一个窗口响应涉及5个步骤:

  4. 系统中发生了某个事件;

  5. Windows把这个事件翻译为消息,然后把它放到消息队列中
  6. 应用程序从消息队列中接收到这个消息,把它存放在TMsg记录中
  7. 应用程序把消息传递给一个适当的窗口过程;
  8. 窗口过程响应这个消息并进行处理

步骤3和步骤4构成了应用程序的消息循环。消息循环往往是Windows应用程序的核心,因为消息循环使一个应用程序能够响应外部的事件。消息循环的任务就是从消息队列中检索消息,然后把消息传递给适当的窗口。

在Win32SDK中提供了与消息实现相关的一系列方法,具体而言,主要函数有以下几种。

注册窗口函数:RegisterClass()和RegisterClassEx()

函数RegisterClass()和RegisterClassEx()注册窗口类,该窗口类会在随后调用CreateWindow()函数或CreateWindowEx()函数中使用。两个函数的原型定义如下:

  1. ATOM RegisterClass(
  2. CONST WNDCLASS *lpWndClass
  3. );
  4. ATOM RegisterClassEx
  5. CONST WNDCLASSEX *Ipwcx
  6. );

其中,参数lpWndClass或Ipwcx指向一个WNDCLASS结构的指针。在将它传递给函数之前,必须在该结构中填充适当的类属性。
如果函数成功,返回值是唯一标识已注册类的一个原子;如果函数失败,返回值为0。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。如果函数成功,返回值是唯一标识已注册类的一个原子;如果函数失败,返回值为0。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。

创建窗口函数:CreateWindow()和CreateWindowEx()

函数CreateWindow()和CreateWindowEx()负责创建窗口,窗口可能是一个重叠式窗口、弹出式窗口或子窗口。两个函数的原型定义如下:

  1. HWND CreateWindow(
  2. LPCTSTR lpClassName,
  3. LPCTSTR lpWindowName,
  4. DWORD dwStyle,
  5. int x,
  6. int y,
  7. int nWidth,
  8. int nHeight,
  9. HWND hWndParent,
  10. HMENU hMenu,
  11. HINSTANCE hInstance,
  12. LPVOID lpParam
  13. );
  14. HWND CreateWindowEx(
  15. DWORD dwExStyle,
  16. LPCTSTR lpClassName,
  17. LPCTSTR lpWindowName,
  18. DWORD dwStyle,
  19. int x,
  20. int y,
  21. int nWidth,
  22. int nHeight,
  23. HWND hWndParent,
  24. HMENU hMenu,
  25. HINSTANCE hInstance,
  26. LPVOID lpParam
  27. );

这两个函数负责创建窗口,指定窗口类、窗口标题、窗口风格以及窗口的初始位置及大小(可选的)。函数也指示该窗口的父窗口或所属窗口(如果存在的话)及窗口的菜单。若要使用除CreateWindow()函数支持的风格以外的扩展风格,则使用CreateWindowEx()函数代替CreateWindow()函数。其中:

  • lpClassName:指向注册窗口类名的指针,由之前调用的RegisterClass()函数或RegisterClassEx()函数创建。
  • lpWindowName:指向一个指定窗口名的空结束的字符串指针。如果窗口有标题条,则窗口名称显示在标题条中。当使用CreateWindow()函数创建控件时,可使用lpWindowName来指定控制文本。
  • dwStyle:指定创建窗口的风格。该参数可以是下列窗口风格的组合:
    • WS_BORDER:创建一个单边框的窗口;
    • WS_CAPTION:创建一个有标题条的窗口(包括WS_BODER风格);
    • WS_CHILD:创建一个子窗口,这个风格不能与WS_POPUP风格合用;
    • WS_CHLDWINDOW:与WS_CHILD样式相同;
    • WS_CLIPCHILDREN:在创建父窗口时使用该样式,当在父窗口中执行绘制操作时,并不绘制子窗口占用的区域;
    • WS_CLIPSIBLINGS:当两个窗口相互重叠时,设置了该样式的子窗口重绘时不能绘制被重叠的部分;
    • WS_DISABLED:创建一个初始状态为禁止的子窗口,一个禁止状态的窗口不能接收来自用户的输入信息;
    • WS_DLGFRAME:创建一个带对话框边框风格的窗口,这种风格的窗口不能带标题条;
    • WS_GROUP:指定一组控件的第一个控件;
    • WS_HSCROLL:创建一个有水平滚动条的窗口;
    • WS_ICONIC:创建一个初始状态为最小化状态的窗口,与WS_MINIMIZE风格相同;
    • WS_MAXIMIZE:创建一个初始状态为最大化状态的窗口;
    • WS_MAXIMIZEBOX:创建一个带有最大化按钮的窗口,该风格不能与WS_EX_CONTEXTHELP风格同时出现,同时必须指定WS_SYSMENU风格;
    • WS_OVERLAPPED:创建一个层叠的窗口,一个层叠的窗口有一个标题条和一个边框,与WS_TILED风格相同;
    • WS_OVERLAPPEDWINDOW:创建一个具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、WS_MINIMIZEBOX、WS_MAXIMIZEBOX风格的层叠窗口,与WS_TILEDWINDOW风格相同;
    • WS_POPUP:创建一个弹出式窗口;
    • WS_POPUPWINDOW:创建一个具有WS_BORDER、WS_POPUP、WS_SYSMENU风格的窗口,WS_CAPTION和WS_POPUPWINDOW必须同时设定才能使窗口控制菜单可见;
    • WS_SIZEBOX:创建一个可调边框的窗口,与WS_THICKFRAME风格相同;
    • WS_SYSMENU:创建一个在标题条上带有窗口菜单的窗口,必须同时设定WS_CAPTION风格;
    • WS_TABSTOP:创建一个控件,这个控件在用户按下Tab键时可以获得键盘焦点;
    • WS_THICKFRAME:创建一个具有可调边框的窗口,与WS_SIZEBOX风格相同;
    • WS_TILED:创建一个层叠的窗口,与WS_OVERLAPPED风格相同;
    • WS_TILEDWINDOW:创建一个具有WS_OVERLAPPED、WS_CAPTION、WS_SYSMENU、WS_THICKFRAME、WS_MINIMIZEBOX和WS_MAXMIZEBOX风格的层叠窗口,与WS_OVERLAPPEDWINDOW风格相同;
    • WS_VISIBLE:创建一个初始状态为可见的窗口;
    • WS_VSCROLL:创建一个有垂直滚动条的窗口。
  • X:指定窗口的初始水平位置。
  • Y:指定窗口的初始垂直位置。
  • nWidth:指定窗口的宽度。
  • nHeight:指定窗口的高度。
  • hWndParent:指向被创建窗口的父窗口或所有者窗口的句柄。
  • hMenu:菜单句柄,或依据窗口风格指明一个子窗口标识。
  • hlnstance:与窗口相关联的模块实例的句柄。
  • lpParam:指向一个值的指针,该值传递给窗口WM_CREATE消息。

CreateWindowEx()函数与CreateWindow()函数相比,增加了窗口的扩展样式,用dwExStyle参数来指明。
如果函数成功,返回值为新窗口的句柄,如果函数失败,返回值为NULL。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。

接收消息函数:GetMessage()、PeekMessage()和WaitMessage()

GetMessage()函数从调用线程的消息队列里取得一个消息并将其放于指定的结构。该函数的原型定义如下:

  1. BOOL GetMessage(
  2. LPMSG lpMsg,
  3. HWND hWnd,
  4. UINT wMsgFilterMin,
  5. UINT wMsgFilterMax
  6. );

此函数可取得与指定窗口联系的消息和由PostThreadMesssge()寄送的线程消息。此函数接收一定范围的消息值。GetMessage()不接收属于其他线程或应用程序的消息。获取消息成功后,线程将从消息队列中删除该消息。函数会一直等待直到有消息到来才有返回值。
其中:
● lpMsg:指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。
● hWnd:取得其消息的窗口的句柄。当其值取NULL时,GetMessage()为任何属于调用线程的窗口检索消息,线程消息通过PostThreadMessage()寄送给调用线程。
● wMsgFilterMin:指定被检索的最小消息值的整数。
● wMsgFilterMax:指定被检索的最大消息值的整数。
如果函数取得WM_QUIT之外的其他消息,返回非零值。如果函数取得WM_QUIT消息,返回值是零。如果出现了错误,返回值是-1。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。
除了GetMessage()函数外,PeekMessage()函数和WaitMessage()函数也可以用于消息的接收。
PeekMessage()函数用于查看应用程序的消息队列,该函数的原型定义如下:

  1. BOOL PeekMessage(
  2. LPMSG lpMsg,
  3. HWND hWnd,
  4. UINT wMsgFilterMin,
  5. UINT wMsgFilterMax,
  6. UINT wRemoveMsg
  7. );

如果消息队列中有消息就将其放入lpMsg所指的结构中,不过,与GetMessage()不同的是,PeekMessage()函数不会等到有消息放入队列时才返回。同样,如果hWnd为NULL,则PeekMessage()获取属于调用该函数应用程序的任一窗口的消息,如果hWnd=-1,那么函数只返回把hWnd参数为NULL的PostAppMessage()函数送去的消息。如果wMsgFilterMin和wMsgFilterMax都是0,则PeekMessage()就返回所有可得到的消息。函数获取之后将删除消息队列中的除WM_PAINT消息之外的其他消息,而WM_PAINT则在其处理之后才被删除。
其中:
● lpMsg:指向MSG结构的指针,该结构从线程的消息队列里接收消息信息。
● hWnd:其消息被检查的窗口句柄。
● wMsgFilterMin:指定被检索的最小消息值的整数。
● wMsgFilterMax:指定被检索的最大消息值的整数。
● wRemoveMsg:确定消息如何被处理。此参数可取下列值之一:
- PM_NOREMOVE:PeekMessage()处理后,消息不从队列里除掉;
- PM_REMOVE:PeekMessage()处理后,消息从队列里除掉;
- PM_NOYIELD:可将该参数随意组合到PM_NOREMOVE或PM_REMOVE,此标志使系统不释放等待调用程序空闲的线程;
- PM_QS_INPUT:Windows NT5.0和Windows98,只处理鼠标和键盘消息;
- PM_QS_PAINT:Windows NT5.0和Windows98,只处理画图消息;
- PM_QS_POSTMESSAGE:Windows NT5.0和Windows98,只处理所有被寄送的消息,包括计时器和热键;
- PM_QS_SENDMESSAGE:Windows NT5.0和Windows98,只处理所有发送消息。
如果消息可得到,函数PeekMessage()的返回为非零值;如果没有消息可得到,返回值是零。
当一个应用程序无事可做时,WaitMessage()函数将控制权交给另外的应用程序,同时将该应用程序挂起,直到一个新的消息被放入应用程序的队列之中才返回。该函数的原型定义如下:

  1. BOOL WaitMessage(VOID);

如果函数调用成功,返回非零值;如果函数调用失败,返回值是0。若想获得更多错误信息,可以调用GetLastError()函数获得错误号。

转换消息函数:TranslateMessage()

TranslateMessage()函数将虚拟键消息转换为字符消息。字符消息被寄送到调用线程的消息队列里,在下一次线程调用函数GetMessage()或PeekMessage()时被读出。该函数的原型定义如下:

  1. BOOL TranslateMessage(
  2. const MSG *lpMsg
  3. );

其中,lpMsg指向含有消息的MSG结构的指针,该结构里含有用函数GetMessage()或PeekMessage()从调用线程的消息队列里取得的消息信息。
如果消息被转换(即字符消息被寄送到调用线程的消息队列里),返回非零值。如果消息是WM_KEYDOWN、WM_KEYUP、WM_SYSKEYDOWN或WM_SYSKEYUP,返回非零值,不考虑转换。如果消息没被转换(即字符消息没被寄送到调用线程的消息队列里),返回值是0。

分发消息函数:DispatchMessage()

DispatchMessage()函数分发一个消息给窗口程序。该函数的原型定义如下:

  1. LRESULT DispatchMessage(
  2. const MSG *lpmsg
  3. );

其中,lpmsg指向含有消息的MSG结构的指针。
返回值是窗口程序返回的值。尽管返回值的含义依赖于被调度的消息,但返回值通常被忽略。
在一个典型的Win32程序的主体中,通常使用while语句实现消息循环。程序通过GetMessage()函数从与某个线程相对应的消息队列里面把消息取出来,放到类型为MSG的消息变量msg中;然后TranslateMessage()函数把消息转化为字符消息,并存放到相应的消息队列里面;最后DispatchMessage()函数把消息分发到相关的窗口过程去处理。窗口过程根据消息的类型对不同的消息进行相关的处理。在SDK编程过程中,用户需要在窗口过程中分析消息的类型及其参数的含义。

MFC下消息实现

MFC类库是一套Windows下C++编程的最为流行的类库。MFC的框架结构合理地封装了Win32API函数,并设计了一套方便的消息映射机制。在MFC的框架结构下,“消息映射”是通过巧妙的宏定义形成一张消息映射表格来进行的。这样一旦消息发生,框架就可以根据消息映射表格来进行消息映射和命令传递。
在MFC下建立消息映射和消息处理的通常步骤如下:
1)定义消息。Microsoft推荐用户自定义消息至少是WM_USER+100,因为很多新控件也要使用WM_USER消息,示例如下:

  1. #define WM_MYMESSAGE (WM_USER + 100)

2)实现消息处理函数。该函数使用WPRAM和LPARAM参数,并返回LPESULT。

  1. LPESULT CMainFrame::OnMyMessage(WPARAM wParam, LPARAM lParam)
  2. {
  3. // TODO: 处理用户自定义消息,填空就是要填到这里。
  4. return 0;
  5. }

3)在类的头文件的AFX_MSG块中说明消息处理函数

  1. // {{AFX_MSG(CMainFrame)
  2. afx_msg LRESULT OnMyMessage(WPARAM wParam, LPARAM lParam);
  3. // }}AFX_MSG
  4. DECLARE_MESSAGE_MAP()

4)在用户类的消息块中,使用ON_MESSAGE宏指令将消息映射到消息处理函数中

  1. ON_MESSAGE( WM_MYMESSAGE, OnMyMessage )