上一章节中我们带大家编写了第一个Windows程序,并且带大家学习了注册窗口,创建窗口,这一章中我们来学习Windows消息,学习对消息循环处理的原理,并且带领大家学习一些常见的消息。
一.消息基础
1.消息概念及其作用
在Windows平台下,消息组成:
- 窗口句柄
- 消息ID
- 消息的两个附加信息
- 消息产生的时间
- 产生消息时,鼠标的位置
我们来看看微软定义的消息结构到底是怎样的:
typedef struct tagMSG{ HWND hwnd; //接收消息的窗口句柄 UINT message; //消息类型(消息标识符) WPARAM wParam; //关于消息的附加消息 LPARAM lParam; //关于消息的附加消息 DWORD time; //消息的产生时间 POINT pt; //发布消息时的光标位置(以屏幕坐标系表示) }
2.消息的作用
当系统通知窗口工作时,就采用消息的方式派发给窗口的消息处理函数,以完成窗口的工作。
3.派发消息的过程
- 1.根据消息的窗口句柄,找到相对应的窗口
- 2.找到保存窗口数据的内存
- 3.找到消息处理函数
- 4.Wndproc(…){
回到自己的代码(处理消息);
}
4.回调函数(窗口处理函数)
每个窗口都必须有回调函数。在应用程序中定义的回调函数,用于处理发送到窗口的消息WNDPROC类型定义指向此回调函数的指针,wndproc名称时应用程序中定义的函数名称的占位符。
我们来看看官方定义的窗口处理函数的原型:
LRESULT CALLBACK WindowProc( HWND hwnd; //窗口句柄 UINT uMsg; //消息ID WPARAM wParam; //消息附加参数 LPARAM lParam; //消息附加参数 ){......};
在这里为大家解释:
LRESULT是程序返回到Windows的整数数值。它包含程序对特定消息的响应。此值的含义取决于消息代码。
CALLBACK是函数的调用约定。
当系统通知窗口时,会调用窗口处理函数,同时将消息ID等附加消息传给窗口处理函数,在窗口处理函数中,不处理的消息,使用缺省窗口处理函数(如DefWindowProc。
5.浅谈消息相关函数
GetMessage
函数–获取本进程的消息
Bool GetMessage( LPMSG lpMsg; //指向MSG结构的指针,该结构从线程的消息队列中结构消息 HWND hwnd; //要检索其消息的窗口句柄,窗口必须属于当前线程 UINT wMsgFilterMin; //要检索的最低消息值的整数值 UINT wMsgFilterMax; //要检索的最高消息值的整数值 );
TranslateMessage
函数–翻译消息
BOOL TranslateMessage( const MSG* lpMsg; );
二.消息循环处理的原理
1.消息循环处理的阻塞
GetMessage
函数: 从系统中获取消息,将消息从系统中移除,阻塞函数。当无系统消息时,等候下一条消息。
PeekMessage
函数: 以查看的方式从系统中获取消息,可以不将消息从系统中移除,非阻塞函数。当系统无消息时,返回FALSE,继续执行后续代码。
BOOL PeekMessage( LPMSG lpMsg; //指向接收信息的MSG结构的指针 HWND hwnd; //要检索其消息的窗口句柄,窗口必须属于当前线程 UINT wMsgFilterMin; //要检查的消息范围的第一条消息的值 uint wMsgFilterMax; //要检查的消息范围的最后一条消息的值 UINT wRemoveMsg; //指定如何处理消息(删除/不删除消息等) }
学习了PeekMessage函数之后我们来看看我们在上一章中写的消息循环的代码:
MSG msg; while (GetMessage(&msg, nullptr, 0, 0)) { if (!TranslateAccelerator(msg.hwnd,NULL, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } } return (int) msg.wParam; }
那么我们就会发现,我们在消息循环中只使用GetMessage函数的话,那么效率就会很低,因为GetMessage函数经常阻塞。在这里我们学习了PeekMessage函数之后,我们就可以将PeekMessage函数当作侦察兵,先让他去看看有没有消息,如果有消息的话,我们就可以在再去GetMessage等函数。
看看我们优化后的代码:
while (1) { if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) { if (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } else { return 0; } } else { //在空闲的时候,我们想让它做什么就可以填在这里 } }
2.在应用程序中调出控制台调试程序
在我们编写控制台程序的时候,我们可以随时输出来调试程序,那么我们在编写Windows程序的时候,我们无法在程序中直接调试程序,那么我们就需要调出控制台来调试我们的程序,在这里给出在我们编写的Windows程序中调出控制台来调试代码的方法:
- 首先,我们需要定义一个全局变量(HANDLE)类型,用于接收标准输出句柄
- 使用
AllocConsole()
函数在Windows程序中增加DOS窗口 - 使用
GetstdHandle
函数接收标准输出句柄,并且用之前定义的HANDLE类型的全局变量接收
使用示例:
HANDLE g_hOutput = 0; ... AllocConsole(); g_hOutput = GetstdHandle(STD_OUTPUT_HANDLE);
其中,sprintf函数的作用是:将设置格式的数据写入字符串。
我们来看看作用效果:
3.发送消息
SendMessage
函数:发送消息,会等候消息的处理结果
LRESULT SendMessage( HWND hWnd; //窗口的过程句柄将接收消息 UINT Msg; //要发送的消息 WPARAM wParam; //其他的消息特定信息 LPARAM lParem; //其他的消息特定信息 );
返回值(LRESULT类型):返回值指定消息处理的结果,这取决于发送的消息。
PostMessage
函数:投递消息,消息发出后立刻返回,不等候消息的处理结果
BOOL PostMessage( HWND hWnd; //窗口的句柄,窗口过程是接收消息 UINT Msg; //要发布的消息 WPARAM wParam; //其他的消息特定信息 LPARAM lParam; //其他的消息特定消息 );
这里关于两个发送消息函数的不同,大家可以看文档,本章后面的消息队列也会从实操角度讲到。
4.消息分类
- 系统消息–ID范围(0~0x03FF)(1024个)
- 由系统定义的消息,可以在程序中直接使用
- 用户自定义消息–ID范围(ox400~0x7FFF)(31743个)
- 由用户自己定义,满足用户自己的需求,由用户自己发出消息,自己处理
- 自定义消息宏:WM_USER(0x400)