这一块一直是一知半解,目前就正好找个机会好好巩固下。
先上个参考链接:
http://www.cnblogs.com/qinfengxiaoyue/archive/2013/02/12/2910614.html
感谢分享…
然后上一个相关链接:可以先读读这个。
接下来是正文部分:
1. 系统消息队列和应用程序消息队列
Windows系统有自己的消息队列。
系统给每一个应用程序建立一个消息队列,这个消息队列属于建立窗口的线程。
应用程序消息队列用来存放该程序可能的各种窗口消息。
建立窗口的线程必须是为窗口处理各种消息的线程。
2. 消息循环
应用程序中有一段代码,称为消息循环代码。代码在如下链接。
WinMain函数的最后一段就是消息循环代码,简化版代码如下:
1 2 3 4 5 |
while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } |
这里要讲解 GetMessage 、 TranslateMessage、 DispatchMessage 这三个函数做了什么。
Windows为当前运行的应用程序维护一个消息队列,当发生了输入事件之后,Windows会将事件转换成一个消息,然后将消息放到应用程序的消息队列中去。
GetMessage函数的作用是从线程的消息队列中取出消息,以方便对它做一系列的预处理。
- 这个函数接收一定范围内的的消息值。
- 不接收属于其他线程或应用程序的消息。
- 获取消息成功后,线程将从消息队列中删除该消息。
- 函数会一直等待直到有消息到来才有返回值。
TranslateMessage函数的作用是将虚拟键消息转换为字符消息。
- 该函数将虚拟键消息转换为字符消息。
- 字符消息送到调用线程的消息队列里。
下一次调用GetMessage的时候,就可以得到转换的字符消息了。
返回值相关:
- 如果消息被转换(即,字符消息被送到线程的消息队列中),返回非零值。
- 如果消息是 WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, 或 WM_SYSKEYUP,返回非零值,不考虑转换。
- 如果消息没有转换(即,字符消息没被送到线程的消息队列中),返回值是零。
其他信息:
TranslateMessage函数不修改由参数lpMsg指向的消息的内容。
TranslateMessage函数只能用于转换由GetMessage或PeekMessage函数接收到的消息。
- 消息WM_KEYDOWN和WM_KEYUP组合产生一个WM_CHAR或WM_DEADCHAR消息。
- 消息WM_SYSKEYDOWN和WM_SYSKEYUP组合产生一个WM_SYSCHAR或 WM_SYSDEADCHAR 消息。
TtanslateMessage仅为那些由键盘驱动器映射为ASCII字符的键产生WM_CHAR消息。
如果应用程序为其它用途而处理虚拟键消息,不应调用TranslateMessage函数。例如,如果TranslateAccelerator函数返回一个非零值,则应用程序将不调用TranslateMessage函数。
Windows CE:Windows CE不支持扫描码或扩展键标志,因此,它不支持由TranslateMessage函数产生的WM_CHAR消息中的lKeyData参数(lParam)16-24的取值。
按我的理解:
- GetMessage取出消息1
- TranslateMessage转换消息1得到消息2,将消息2放入消息队列。
- 然后继续处理消息1.
DispatchMessage函数的作用是调度一个消息给窗口程序,通常调度从GetMessage取得的消息,消息被调度到的窗口程序即是窗口过程函数。
返回值是窗口程序返回的值,尽管返回值的含义依赖于被调度的消息,但返回值通常被忽略。
它的工作原理实际上是:
- 调用 DispatchMessage(&msg); 将消息结构msg回传给Windows
- 然后Windows将该消息发送给适当的窗口消息处理程序,让它进行处理。
- 处理完消息之后,WndProc(窗口过程函数) 传回到Windows,此时Windows还停留在DispatchMessage呼叫中。
- 在结束DispatchMessage呼叫的处理之后,Windows回到程序中,并且接着从下一个GetMessage呼叫开始消息循环。
完整的过程如下图:
- 操作系统将消息投递到消息队列中 或者 直接调用窗口过程函数(下文会讲解)。
- 程序调用GetMessage函数获得消息
- 程序可以进行一系列的处理,比如 TranslateMessage
- 调用 DispatchMessage 将消息传回到操作系统
- 操作系统调用窗口过程函数,待处理完后返回到程序。
以上是我目前理解的消息的全部过程,可能依旧有误,已经不敢说自己理解了消息了,万一又错了呢。
3. 队列消息与非队列消息
消息可以分为 队列消息 和 非队列消息,这个区分主要是根据消息有没有进入到线程的消息队列中。
队列化的消息由Windows放入程序(线程的)消息队列中,在程序的消息循环中,重新传回给Windows并由系统分配给窗口消息处理程序。
非队列化的消息在Windows呼叫窗口时直接送给窗口的消息处理程序 – 窗口过程函数。
也就是说,队列化的消息最初是被「发送」给消息队列,而非队列化的消息则直接「发送」给窗口消息处理程序,然后他们最终都到达了程序的消息处理中心,窗口过程函数。
队列消息基本上是用户输入的结果,以 比如 击键(如WM_KEYDOWN和WM_KEYUP消息)、击键产生的字符(WM_CHAR)、鼠标移动(WM_MOUSEMOVE)和鼠标按钮(WM_LBUTTONDOWN)的形式给出。
队列化消息还包含 时钟消息(WM_TIMER)、重绘消息(WM_PAINT)和 退出消息(WM_QUIT)。
非队列消息则是除队列消息外的其他消息,在许多情况下,非队列化消息来自呼叫特定的Windows函数。
举几个例子:
当WinMain调用 CreateWindow函数时,Windows将建立窗口并在处理中给窗口消息处理程序发送一个WM_CREATE消息。
当WinMain调用 ShowWindow函数时, Windows将给窗口消息处理程序发送WM_SIZE和WM_SHOWWINDOW消息。
当WinMain调用 UpdateWindow 时, Windows将给窗口消息处理程序发送WM_PAINT消息。
键盘或鼠标操作时发出的消息信号,也能产生非队列消息。
例如,用键盘或鼠标选择了一个菜单项时,键盘或鼠标按下的消息就是队列消息,而说明菜单项已选中的 WM_COMMAND 消息则可能就是非队列化的。
4. SendMessage 和 PostMessage 的区别
之前面试经常问这个问题,但是总要想好久才想起来,这次经过 队列消息 和 非队列消息的学习,就很好理解了。
SendMessage就像发送非队列消息一样,直接调用窗口过程函数,然后得到返回值。
PostMessage就像发送队列消息,把消息放到消息队列中去,就直接退出了。
如下图:
5. GetMessage 和 PeekMessage 的区别
这个就比较简单了,两个函数主要有以下两个区别:
- GetMessage将等到有合适的消息时才返回,而PeekMessage只是看一下消息队列,有就拿出,没用就算了。
- GetMessage会将消息从队列中删除,而PeekMessage可以设置最后一个参数wRemoveMsg来决定是否将消息保留在队列中。
6. 消息队列与窗口、线程的关系
面试经常问的,引用CSDN博客里的回答:
线程是用来干活的, 一个进程内部可以有好几个干活的线程。
线程可以分为工作线程和UI线程:
带有界面的线程叫 UI 线程, 每个 UI 线程内会有一个消息队列,一个线程在第一调用 GDI、User 窗口函数时创建消息队列。
窗口属于创建窗口的线程,这些同一线程的窗口共享同一个消息循环,为了同步原因,最好只在创建窗口的线程内访问窗口。