1 SendMessage & PostMessage
在使用消息的过程中,这两个函数的使用率是最高的。初学者有时会搞不清楚这两个发送消息的函数的使用场景,容易误用。所以放在这里一起说。其实上面已经对 SendMessage 做了很多的介绍,所以在这儿的重点会放在 PostMessage 上。相较 SendMessage而言,PostMessage 的工作要轻松许多,只要找到知道那个的窗口句柄所在的线程,把消息放到该线程的消息队列里就可以了,完全不理会这条消息最终的命运,是不是被正确处理了。
这一点,从 PostMessage 和 SendMessage 的返回值的不同也有体现。PostMessage 函数的返回值是 BOOL 类型,体现的是投递操作是否成功。投递操作是有可能失败的,尽管我们不愿意同时也确实很少看到。例如,目标线程的消息队列已经满(在 16 位时代出现概率较高),或者更糟糕,目标线程根本就没有消息队列。
当然,PostMessage 也要检查窗口句柄的合法性,不过和SendMessage 不同的一点是,它允许窗口句柄是 NULL。在此情况下,对它的调用就等价于调用 PostThreadMessage 向自身所在线程投递一条消息。
从上面的描述可以很容易地看出,PostMessage 和 SendMessage 的本质区别在于前者发出的消息是异步处理的,而后者发出的消息是同步处理的。理解这一点非常重要。
2 GetMessage&PeekMessage
我们知道GetMessage用来检查线程的消息队列,如果有消息就取出该消息到一个传入的 MSG 结构中并返回,没有消息,就等待。等待时线程处于休眠状态,CPU被分配给系统内的其他线程使用。
需要注意的是,由其它线程 Send 过来的消息,会在这里就地处理(即调用相应的窗口回调函数),而不会返回给调用者。
对于PeekMessage可以描述为窥探线程的消息队列,无论队列中有没有消息,这个函数都立即返回。它的参数列表与 GetMessage 基本一致,只是多了一个标志参数。这个标志参数指定了如果队列中如果有消息的话,PeekMessage 的行为。如果该标志中含有PM_REMOVE,则 PeekMessage 会把新消息返回到 MSG 结构中,正如 GetMessage 的行为那样。如果标志中指定了 PM_NOREMOVE,则不会取出任何消息。
PeekMessage和GetMessage函数的主要区别有:
(1)GetMessage的主要功能是从消息队列中“取出”消息,消息被取出以后,就从消息队列中将其删除;而PeekMessage的主要功能是“窥视”消息,如果有消息,就返回true,否则返回false。也可以使用PeekMessage从消息队列中取出消息,这要用到它的一个参数(UINT wRemoveMsg),如果设置为PM_REMOVE,消息则被取出并从消息队列中删除;如果设置为PM_NOREMOVE,消息就不会从消息队列中取出。
(2)如果GetMessage从消息队列中取不到消息,则线程就会被操作系统挂起,等到OS重新调度该线程时,两者的性质不同:使用GetMessage线程仍会被挂起,使用PeekMessage线程会得到CPU的控制权,运行一段时间。
(3)GetMessage每次都会等待消息,直到取到消息才返回;而PeekMessage只是查询消息队列,没有消息就立即返回,从返回值判断是否取到了消息。
我们也可以说,PeekMessage是一个具有线程异步行为的函数,不管消息队列中是否有消息,函数都会立即返回。而GetMessage则是一个具有线程同步行为的函数,如果消息队列中没有消息的话,函数就会一直等待,直到消息队列中至少有一条消息时才返回。
如果消息队列中没有消息,PeekMessage总是能返回,这就相当于在执行一个循环,如果消息队列一直为空, 它就进入了一个死循环。GetMessage则不可能因为消息队列为空而进入死循环。
联系: 在Windows的内部,GetMessage和PeekMessage执行着相同的代码,Peekmessage和Getmessage都是向系统的消息队列中取得消息,并将其放置在指定的结构。
区别:
PeekMessage:有消息时返回TRUE,没有消息返回FALSE
GetMessage:有消息时且消息不为WM_QUIT时返回TRUE,如果有消息且为WM_QUIT则返回FALSE,没有消息时不返回。
GetMessage:取得消息后,删除除WM_PAINT消息以外的消息。
PeekMessage:取得消息后,根据wRemoveMsg参数判断是否删除消息。PM_REMOVE则删除,PM_NOREMOVE不删除。
(The PeekMessage function normally does not remove WM_PAINT messages from the queue. WM_PAINT messages remain in the queue until they are processed. However, if a WM_PAINT message has a null update region, PeekMessage does remove it from the queue.)
不能用PeekMessage从消息队列中删除WM_PAINT消息,从队列中删除WM_PAINT消息可以令窗口显示区域的失效区域变得有效(刷新窗口),如果队列中包含WM_PAINT消息程序就会一直while循环了。
3 DisPatchMessage
DispatchMessage 拿到了 MSG 结构,开始自己的一套办事流程。
首先,检查消息指定的目标窗口句柄。看系统内(实际上是本线程内)是不是确实存在这样一个窗口,如果没有,那说明这个消息已经不会有需要对它负责的人选了,那么这个消息就会被丢弃。
如果有,它就会直接调用目标窗口的回调函数。终于看到,我们写的回调函数出场了,这就是“恰当的时机”之一。当然,为了叙述清晰,此处省略了系统做的一些其他处理,这样,对于系统来说,一条投递消息就处理完成。
4 TranslateMessage&TranslateAccelerator
这个函数在本质上与消息机制的关系不大,绝大多数的消息循环中都出现它的身影是因为绝大多数的程序员都不知道这个函数真正是干什么的,仅仅是出于惯例或者初学时教科书上给出的范例。这个函数的作用主要和输入有关,它会把 WM_KEYDOWN 和 WM_KEYUP 这样的消息恰当地、适时地翻译出新的消息来,如 WM_CHAR。如果你确信某个线程根本不会有用户输入方面的需求,基本上可以安全地将之从循环中移除。即把一个virtual-key消息转化成字符消息(character message),并放到当前线程的消息队列中,消息循环下一次取出处理。
可以和它相提并论的就是列出的 TranslateAccelerator 函数,这个函数会把用户输入根据指定的加速键(Accelerator)表翻译为适当的命令消息,即将快捷键对应到相应的菜单命令。
5 BroadcastSystemMessage
我们一般所接触到的消息都是发送给窗口的, 其实, 消息的接收者可以是多种多样的,它可以是应用程序(applications), 可安装驱动(installable drivers), 网络设备(network drivers), 系统级设备驱动(system-level device drivers)等,
BroadcastSystemMessage这个API可以对以上系统组件发送消息。
6 MsgWaitForMultipleObjiects(Ex)
这是一个在讲到消息相关的内容时,十有八九会被人遗忘的 API。它属于传统的 ITC、IPC 和 Windows 特有的消息机制的交叉地带。不过,在 Windows 平台上,如果还没有了解并掌握这个函数,那一定不能称其为专家。
这个函数揭示了以下平时不太为人所注意的细节:
1、 消息和内核对象,有千丝万缕的联系
2、 消息和内核对象可以按照相似的方式去处理
如果说,SendMessageTimeout 是 Windows 平台下最强大的发送消息的机制,那么,MsgWaitForMultipleObjects[Ex] 就是最强大等待机制,它是 WaitMessage 和 WaitFor… 函数族的集大成者。根据我们上面使用 WaitMessage 和 PeekMessage 结合使用可以取代 GetMessage 的论断,我们也可以这样说,MsgWaitForMultipleObjects[Ex]是最强大的消息循环发动机。
7 VC消息泵
while(GetMessage(&msg, NULL, 0, 0)) } |
TranslateMessage(转换消息):
用来把虚拟键消息转换为字符消息。由于Windows对所有键盘编码都是采用虚拟键的定义,这样当按键按下时,并不得字符消息,需要键盘映射转换为字符的消息。
TranslateMessage函数 用于将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出。当我们敲击键盘上的某个字符键时,系统将产生WM_KEYDOWN和WM_KEYUP消息。这两个消息的附加参数(wParam和lParam)包含的是虚拟键代码和扫描码等信息,而我们在程序中往往需要得到某个字符的ASCII码,TranslateMessage这个函数就可以将WM_KEYDOWN和WM_ KEYUP消息的组合转换为一条WM_CHAR消息(该消息的wParam附加参数包含了字符的ASCII码),并将转换后的新消息投递到调用线程的消息队列中。注意,TranslateMessage函数并不会修改原有的消息,它只是产生新的消息并投递到消息队列中。
也就是说TranslateMessage会发现消息里是否有字符键的消息,如果有字符键的消息,就会产生WM_CHAR消息,如果没有就会产生什么消息。
DispatchMessage(分派消息):
把TranslateMessage转换的消息发送到窗口的消息处理函数,此函数在窗口注册时已经指定。
首先,GetMessage从进程的主线程的消息队列中获取一个消息并将它复制到MSG结构,如果队列中没有消息,则GetMessage函数将等待一个消息的到来以后才返回。如果你将一个窗口句柄作为第二个参数传入GetMessage,那么只有指定窗口的的消息可以从队列中获得。GetMessage也可以从消息队列中过滤消息只接受消息队列中落在范围内的消息。这时候就要利用GetMessage/PeekMessage指定一个消息过滤器。这个过滤器是一个消息标识符的范围或者是一个窗体句柄,或者两者同时指定。当应用程序要查找一个后入消息队列的消息是很有用。WM_KEYFIRST 和 WM_KEYLAST 常量用于接受所有的键盘消息。 WM_MOUSEFIRST 和 WM_MOUSELAST 常量用于接受所有的鼠标消息。
然后TranslateAccelerator判断该消息是不是一个按键消息并且是一个加速键消息,如果是,则该函数将把几个按键消息转换成一个加速键消息传递给窗口的回调函数。处理了加速键之后,函数TranslateMessage将把两个按键消息WM_KEYDOWN和WM_KEYUP转换成一个 WM_CHAR,不过需要注意的是,消息WM_KEYDOWN,WM_KEYUP仍然将传递给窗口的回调函数。
处理完之后,DispatchMessage函数将把此消息发送给该消息指定的窗口中已设定的回调函数。如果消息是WM_QUIT,则 GetMessage返回0,从而退出循环体。
应用程序可以使用PostQuitMessage来结束自己的消息循环。通常在主窗口的 WM_DESTROY消息中调用。
注意:PostQuitMessage这个函数的名字具有迷惑性。事实上,它本身并不会投递任何消息,而是偷偷在系统内部置了一个标志,当调用 GetMessage 时会检测此标志位。若此标志位被置位,而且队列中已经没有别的符合条件的投递消息,则 GetMessage 返回 FALSE,用以终止消息循环。