文章目录
一、windows 消息类型
-
Windows系统定义消息:
与窗口的内部运作有关,如创建窗口,绘制窗口,销毁窗口等。这种消息以WM_打头,如:WM_CREATE, WM_PAINT, WM_MOUSEMOVE -
控件通知消息
针对标准Windows控件的消息。这些控件包括:按钮(Button)、 组合框(ComboBox)、编辑框(TextBox)、列表框(ListBox)、 ListView控件、Treeview控件、 工具条(Toolbar)、 菜单(Menu)等。每种消息以不同的字符串打头 -
自定义消息
二、windows 消息队列
- 系统消息队列
这是windows系统自身唯一的Queue,设备驱动把输入转化成消息存在系统队列中,然后系统会把此消息放到目标窗口所在的线程的消息队列中 - 应用程序线程消息队列
每一个GUI线程都会维护一个线程消息队列。为避免给non-GUI线程创建消息队列,所有线程创建时并没有消息队列,仅当线程第一次调用GDI函数时系统才给线程创建一个消息队列,然后线程消息队列中的消息会被送到相应的窗口过程去处理。CWinThread专门负责线程创建,它可以创建用户界面线程,及工作者线程,其中用户界面线程是包含消息队列的,而工作者线程是不包含消息队列的
三、windows 队列消息和非队列消息
- 队列消息
消息会先保存在消息队列中,消息循环会从此队列中取消息并分发到各窗口处理、如鼠标,键盘消息,WM_KEY*、WM_TIMER、WM_PAINT - 非队列消息
消息会绕过系统消息队列和应用程序线程消息队列,直接发送到窗口过程被处理,如: WM_ACTIVATE, WM_SETFOCUS, WM_SETCURSOR
四、windows 消息优先级
GetMessage()
从队列中按消息优先级获取消息,优先级由高到低:
- 其他线程通过
SendMessage()
发送给该窗口线程的消息,GetMessage()
直接调用窗口回调函数处理消息,直到没有该种类消息,再按顺序查找下面类型的消息 - 其他线程通过
PostMessage()
发送给该窗口线程的消息 WM_QUIT
消息,队列中轮询到该消息,GetMessage()
返回0,退出消息循环- 系统输入消息,如键盘,鼠标
WM_PAINT
消息WM_TIMER
消息
五、TranslateMessage()
用于将虚拟键值信息转换为字符信息,这一步是可选的。当我们敲击键盘上的某个字符键时,系统将产生 WM_KEYDOWN
和 WM_KEYUP
消息,这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage
可以将 WM_KEYDOWN
和 WM_ KEYUP
消息的组合转换为一条WM_CHAR
消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage
函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中
六、DispatchMessage()
把 TranslateMessage
转换的消息交给操作系统,操作系统通过注册窗口类时指定的窗口过程对消息进行处理
七、SendMessage()与PostMessage()的区别
PostMessagex()
异步处理,发送的消息是队列消息,把消息放到指定窗口所在的线程消息队列中后立即返回,不等消息处理完成
SendMessage()
同步处理,发送的消息是非队列消息,发送的消息不会被加入到消息队列中,直接把消息送到窗口过程处理,处理完了才返回
八、GetMessage()和PeekMessage()的区别
GetMessage()
阻塞方式,直到消息队列中有消息才返回,并且会将消息从队列中删除,如果没有消息则阻塞等待
PeekMessage()
非阻塞方式,不管消息队列是否有消息均返回,通过参数决定是否将消息从队列删除,类似于查询队列
当获取到 WM__QUIT
消息时,返回0,否则返回非零值,当输入参数无效时返回-1
九、消息死锁
如果一个线程通过调用 SendMessage
函数给其他线程发送消息,那么这个线程要等到窗口处理函数处理完之后,SendMessage
函数才会返回,这个线程才能继续执行。假如接收线程在处理消息过程中让出来控制权,如接收线程也调用了 SendMessage
,那么双方线程都不能继续执行,都需要等待SendMessage
函数返回,这就有可能导致应用程序死锁
为了避免应用程序永久死锁,发送方应该考虑使用 SendNotifyMessage
或者 SendMessageTimeout
函数,或者窗口过程应该在调用上面那些可能让出控制权的函数之前先处理消息,如调用ReplyMessage
函数,以便于让对方能够继续执行,从而避免死锁
十、广播消息
消息广播简化了系统中需要向多个接收者发送消息的情况。通过 BroadcastSystemMessage
函数,定义好消息的接收者就是了。这个时候只需要定义一个或者多个接收者类型就行了,这些类型指的是应用程序,安装型驱动程序,网络驱动程序和系统级的设备驱动程序,系统会发送广播消息给每个指定类型的所有成员。
系统发送广播消息一般是在系统级设备驱动程序发生变化的时候而做出的响应,驱动程序可以广播消息给应用程序和其他组件来通知他们发生了改变。例如,当用户在软驱里面插入磁盘的时候,软驱的驱动程序就会广播一个消息,相关组件就会对这个消息作出反应
十一、Windows 消息系统组成
- 消息队列。Windows为每个应用程序维护一个消息队列,Windows消息队列是以GUI线程来分组的,也就是说每个GUI线程都有自己的消息队列
- 消息循环。应用程序从消息队列中获取消息,再把它分派给适当的某个窗口,然后继续从消息队列中获取下一条消息,再分派给适当的某个窗口,循环进行
- 窗口过程。每个窗口都有一个窗口过程来处理分派给窗口的消息,窗口过程是一个回调函数,处理了一个消息后,它通常要返回一个值给Windows
十一、Windows 消息处理过程
- 当鼠标、键盘事件被触发后,相应的设备驱动程序就把事件转换成消息,然后存到windows系统的消息队列中
- windows从系统消息队列取出的消息,并根据
hwnd
字段送往对应窗口所在线程的消息队列,同时消息从系统消息队列删除 - 应用程序通过
GetMessage()
从自己的消息队列中取出消息,如果队列中没有消息则阻塞 - 取出的消息通过
TranslateMessage()
进行转换,将虚拟键值信息转换为需要的字符信息 - 调用
DispatchMessage()
将消息再转给操作系统 - 操作系统调用注册窗口类时设置的窗口过程回调函数去处理消息,若不处理则会调用
DefWindowProc
函数做默认处理 - 循环 c~f 过程
十二、Windows 消息处理代码示例
#include <windows.h>
#include <stdio.h>
// 窗口过程回调函数,DispatchMessage()后由操作系统调用
LRESULT CALLBACK MyWinProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
) {
switch (uMsg) {
case WM_CHAR:
MessageBox(hwnd, "Pressed key xxx", "按键按下", MB_OK); break;
case WM_LBUTTONDOWN:
MessageBox(hwnd,"left button down","左键按下",MB_OK); break;
case WM_CLOSE:
DestroyWindow(hwnd); break; // 点击窗口关闭按钮,发送 WM_DESTROY 消息到应用消息队列,此时程序并没有退出
case WM_DESTROY:
PostQuitMessage(0); break; // 发送 WM_QUIT 消息到应用消息队列,GetMessage()返回0退出消息循环,此时程序退出
default :
return DefWindowProc(hwnd,uMsg,wParam,lParam); // 如果没有设置自定义消息处理,则使用默认消息处理
}
return 0;
}
// 操作系统调用,参数已经赋值
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
) {
WNDCLASS wndcls; //定义窗口类结构体变量
wndcls.style = CS_HREDRAW|CS_VREDRAW;
wndcls.lpfnWndProc = MyWinProc; // 设置自定义窗口过程,即注册自定义消息处理函数
wndcls.cbClsExtra = 0;
wndcls.cbWndExtra = 0;
wndcls.hInstance = hInstance;
wndcls.hIcon = LoadIcon(NULL, IDI_QUESTION);
wndcls.hCursor = LoadCursor(NULL, IDC_SIZEALL);
wndcls.hbrBackground = (HBRUSH)GetStockObject(DKGRAY_BRUSH); //笔,画刷,字体,调色板
wndcls.lpszMenuName = NULL;
wndcls.lpszClassName = "wenhui";
/*注册窗口类*/
RegisterClass(&wndcls);
/*创建一个具体的窗口*/
HWND hwnd = CreateWindow("wenhui", "红楼梦", WS_OVERLAPPEDWINDOW&~WS_MAXIMIZEBOX,
CW_USEDEFAULT, CW_USEDEFAULT, 600, 400, NULL, NULL, hInstance, NULL);
/*显示创建的一个具体窗口*/
ShowWindow(hwnd, SW_SHOWNORMAL);
/*更新一个窗口的客户区*/
UpdateWindow(hwnd);
MSG msg;
while (GetMessage(&msg, NULL, 0, 0)) { // 只有取到 WM_QUIT 消息后,返回值为0才退出 while 循环
TranslateMessage(&msg); // 转换消息,将虚拟键值信息转换为字符信息
DispatchMessage(&msg); // 发送消息给操作系统,然后由操作系统调用注册窗口类时设置的消息处理回调函数
}
return 0;
}
/*
typedef struct tagMSG {
HWND hwnd; // 窗口句柄
UINT message; // 消息常量标识符
WPARAM wParam; // 16位消息的特定附加信息
LPARAM lParam;
DWORD time; // 消息创建时的时间
POINT pt; // 消息创建时的鼠标位置
} MSG, *PMSG;
*/