windows消息发送与接收

Windows开发中一个重要的概念就是消息。能搞清楚消息的传递和处理,相信可以使我们对Windows程序有更深的理解。

先把消息划分为3类:发送消息(Incomingsent message)、投递消息(Post message)、输入消息(Input message)。其中发送消息是非队列消息,而后两种是队列消息。在线程的消息队列中并不包括非队列消息,而只有队列消息才会在线程的消息队列中。

由上面的分类也可以知道为什么不能通过PostMessage函数来模拟输入动作。因为投递消息将进入到投递消息队列,而输入消息则是进入到输入消息队列中,而且这两个消息队列是在不同的时间进行处理的。

一、发出消息

SendMessage函数族(SendNotifyMessage,SendMessageCallback和SendMessageTimeout)中,从消息的处理方式上来看,所有函数的行为都是一样的:如果发送者和接收者在同一个线程中,那么接收者的窗口过程将被直接调用;如果发送者和接收者在不同的线程中,那么这个消息将被添加到接收者的发送消息队列中,并且将“唤醒”接收者来处理消息。

PostMessage和PostThreadMessage这两个函数将消息添加到接收者的而投递消息队列中,并且唤醒接收者。

用户输入(包括键盘输入、鼠标输入,或者通过SendInput函数产生的输入)所产生的消息被添加到输入消息队列中,并且同样将唤醒接收者。

如下图:

接收者被唤醒意味着,如果接收者被GetMessage、WaitMessage、MsgWaitMultiplieObjects或者其他类似的函数阻塞了,那么接收线程将解除阻塞,这样它能够处理新的消息,就好像添加到队列的消息正是接收线程正在等待的消息。

二、接收消息

在消息的接收端,有三种方法用来接收消息。虽然各种方法之间略有不同,但这些方法都遵循相同的基本规则:

1.  发送消息将被直接递交到窗口过程中。

2.  接收投递消息(通过GetMessage或者PeekMessage函数)。

3.  接收输入消息(通过GetMessage或者PeekMessage函数)。

在消息的处理过程中需要递交消息,这个函数可能像下面这样:

LRESULT  DeliverMessage(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam)
{
//获取对应的窗口过程
      WNDPROC  lpfnWndProc = (WNDPROC)GetWindowLongPtr(hWnd,GWLP_WNDPROC);
//调用窗口过程处理消息
      return CallWindowProc(lpfnWndProc,hWnd,uMsg,wParam,lParam);
}

消息的处理原则之一就是要递交发送消息。这个函数的处理过程像下面这样:

void  DeliverIncomingSentMessages()
{
    while(有一个发送消息){
        MSG msg = 这个消息;
        从发送消息队列中删除这个消息;
        if(这是一个特殊的伪消息){
            处理这个伪消息;
        }else{
            DeliverMessage(msg.hWnd, msg.message, msg.wParam, msg.lParam);
            把返回值交给发送者;
        }
    }
}

在前面这些函数的基础上,我们可以开始分析接收消息的函数了。

PeekMessage的伪码像下面展示的那样:

BOOL  PeekMessage(LPMSG pmsg,HWND hwnd,UINT wMsgFilterMin,UINT wMsgFilterMax,UINT flags)
{
    DeliverIncomingSentMessages();
    if(有某个投递消息满足过滤条件){
        *pmsg = 这个消息;
        if(flags & PM_REMOVE)  从投递消息队列中删除该消息;
        return TRUE;
    }
    if(有某个输入消息满足过滤条件){
        *pmsg = 这个消息;
        if(flags & PM_REMOVE)  从输入消息队列中删除这个消息;
        return TRUE;
    }
    return FALSE;
}

PeekMessage函数首先会递交所有等待处理的发送消息(如果有的话)。在完成这个操作之后,函数将根据设置的过滤条件(wMsgFilterMin和参数wMsgFilterMax分别用于设置被检索消息的最小值和最大值)在投递消息队列中查找相应的消息,如果没有找到,将在输入消息队列中根据过滤条件继续查找。如果找到了一个消息,那么接收该消息(不是递交)并把该消息从对应的消息队列中删除(如果flags参数中包含PM_REMOVE标志)。如果不存在满足过滤条件的消息,那么PeekMessage函数将返回FALSE。

由上可见,在处理各类消息时所采用的策略是不同的。我们不可能过滤掉发送消息,这些消息将肯定会被处理。只能对投递消息和输入消息进行过滤。发送消息在PeekMessage函数内部被派发,而投递消息和输入消息将通过pmsg参数返回给调用者,并且由调用者来决定对这个消息进行什么样的处理(通常的处理是“派发这个消息”,不过也可以不这样做)。

GetMessage函数的处理流程与PeekMessage函数类似,只是GetMessage只有在获得了一个投递消息或者输入消息时才会返回。如果没有获得这样的消息,GetMessage将一直等下去,直到来了一个投递消息或输入消息:

BOOL GetMessage(LPMSG pmsg,HWND hwnd,UINT wMsgFilterMin,UINT wMsgFilterMax)
{
    while(!PeekMessage(pmsg,hwnd,wMsgFilterMin,wMsgFilterMax,PM_REMOVE)){
        WaitMessage();
    }
    return pmsg->message != WM_QUIT;
}

还有一种情况是线程间发送消息的派发。

SendToAnotherThread(…)
{
    将消息添加到接收者的发送消息队列中;
    //这两个函数将等待接收者响应
    if(Function == SendMessage || Function == SendMessageTimeOut){
        while(!收到了消息 && !超时){
            DeliverIncomingSentMessage();
            等待新的消息或者超时(如果是SendMessageTimeOut);
        }
    }
}

消息被添加到目标窗口所在线程的发送消息队列中;在调用了SendMessage和SendMessageTimeout的情况下,发送线程在派发发送消息后将等待目标窗口返回一个结果(或者消息超时)。

综上所述,只有在三种情况下才能派发发送消息:1.在PeekMessage中;2.在GetMessage中;3.线程间的SendMessage。

三、消息生命周期

1.发送消息

前面已经说到过,线程如果向自己的某个窗口发送消息,则接收窗口的窗口过程将被直接调用(这个消息既不进入消息队列,也不会进入消息泵)。

线程间发送消息,则被发送的消息将加入到目标窗口的发送消息队列中。这个消息将在发送消息队列中等待,直到目标窗口所在的线程通过执行PeekMessage,GetMessage或者线程间的SendMessage。当接收消息的线程从窗口过程中返回时,消息的处理结果将被返回到发送线程中。在得到返回结果后,发送线程将继续执行。

如果使用了ReplyMessage函数,那么消息生命周期可能会发生变化。如果接收线程在处理发送消息时调用了ReplyMessage,那么传递给ReplyMessage函数的参数值将被返回给发送线程,就好像接收线程从窗口过程中返回一样。因此,发送线程和接收线程将同时执行。发送线程执行是因为这个线程将不再需要等待消息的结果,而接收线程执行是因为这个线程仍然没有从窗口过程中返回(窗口过程的最终返回值将被忽略,因为接收线程已经向发送线程返回了一个值,并且程序也无法再回到那个时刻来改变那个已经返回的值)。

2.投递消息

投递消息被添加到目标窗口所在的线程的投递消息队列中。这个消息将在投递消息队列中持续等待,直到接收线程调用GetMessage或者PeekMessage函数将消息赋值到程序所提供的MSG结构中。接下来发生的动作将取决于接收这个消息的对象。

虽然,从理论上来说,程序可以对投递消息做任何操作。但是最可能的情况是在主消息泵中得到该消息,主消息泵的代码可能这样:

while (GetMessage(&msg, NULL, 0,0)){
    if(!TranslateAccelerator(hwnd,hacc,&msg)){
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

如果是上面的情况,那么这个消息将首先与加速键表进行比较,如果找到了一个匹配的加速键,将会递交一个WM_COMMAND(或者WM_SYSCOMMAND)消息到hwnd窗口中。在这种情况下,消息的处理过程就结束了,因此这个消息将永远都不会被投递到窗口过程中。

如果没有找到匹配的加速键,这个消息将通常由DispatchMessage函数递交到窗口过程,但也有例外的情况:

○ 如果是线程消息,那么就没有相应的目标窗口。因此,线程消息不会被派发。

○ 如果是WM_TIMER消息,并且消息的lParam参数不为空,那么lParam将被视为一个TIMEPROC回调函数,因此将直接调用这个函数。

3.生成的投递消息

前面分的三类消息并不能涵盖所有的消息,还有一些,我们姑且称之为“特殊类型的消息”。

WM_MOUSEMOVE消息就是其中的一个特殊消息。当鼠标移动的时候,这个消息并不是被作为输入消息添加到输入消息队列中,而是设置一个“鼠标已经移动”的标志。在窗口管理器查找输入消息时,如果发现设置了“鼠标已经移动”这个标志,那么窗口管理器将清除这个标志,并且动态地生成一个WM_MOUSEMOVE消息,然后再把这个消息添加到输入消息的队列中(或者与一个现有的WM_MOUSEMOVE消息合并在一起)。

其它属于这种“动态生成的”特殊消息分别是:WM_PAINT、WM_TIMER、和WM_QUIT。其中前两种消息甚至可以在消息查找过程完成之后再来生成,当没有发现可用的输入消息并且在消息过滤器中明确指出需要这种类型的消息时,才会生成这两种消息(生成WM_QUIT消息的时间甚至比生成窗口绘制消息和计时器消息的时间更晚,因为只有在投递消息队列为空的情况下才会生成这个消息。此外,WM_QUIT消息会忽略消息过滤的设置;也就是说,只要这个消息出现,就肯定会被处理)。

综合考虑前面说的各种情况,将PeekMessage写成下面这种更为复杂的形式:

BOOL PeekMessage(LPMSG pmsg, HWND hwnd, UINT wMsgFilterMin,
UINT wMsgFilterMax, UINT flags)
{
DeliverIncomingSentMessage();
if(有某个投递消息满足过滤条件){
    *pmsg = 这个消息;
    if(flags & PM_REMOVE)  将这个消息从投递消息队列中删除:
    return TURE;
}
//WM_QUIT消息将忽略过滤设置
if(有一个WM_QUIT消息正在等待处理,并且没有投递消息了){
    清除”有WM_QUIT消息正在等待处理”的标志;
    *pmsg = WM_QUIT消息;
    return TRUE;
}
if(鼠标移动了并且输入消息满足过滤条件){
    清除”鼠标已经移动”的标志;
    将WM_MOUSEMOVE消息添加到输入消息队列中;
}
if(有某个输入消息满足过滤条件){
    *pmsg = 这个消息;
    if(flags & PM_REMOVE)  从输入消息队列中删除这个消息;
    return TURE;
}
if(窗口需要进行绘制并且WM_PAINT消息满足过滤条件){
    *pmsg = WM_PAINT消息;
    return TRUE;
}
if(计时器被触发了,并且WM_TIMER消息满足过滤条件){
    将一个WM_TIMER消息添加到投递消息队列中;
    *pmsg = 这个消息;
    if(flags & PM_REMOVE)  从投递消息队列中删除这个消息;
    return TRUE;
}
return FALSE;
}

WM_MOUSEMOVE和WM_TIMER消息是非常有意思的:当需要生成这些消息时,它们才成为队列中正真的消息。如果程序在调用PeekMessage函数时使用了PM_NOREMOVE标志,那么这会导致一些微妙的问题。这些消息将停留在消息队列中,随后的鼠标移动消息将会和这个驻留的鼠标移动消息合并在一起。

时间: 2024-09-29 08:07:52

windows消息发送与接收的相关文章

Chromium的IPC消息发送、接收和分发机制分析

由于Chromium采用多进程架构,因此会涉及到进程间通信问题.通过前面一文的学习,我们知道Browser进程在启动Render进程的过程中会建立一个以UNIX Socket为基础的IPC通道.有了IPC通道之后,接下来Browser进程与Render进程就以消息的形式进行通信.我们将这种消息称为IPC消息,以区别于线程消息循环中的消息.本文就分析Chromium的IPC消息发送.接收和分发机制. 老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注! Chrom

DICOM医学图像处理:DIMSE消息发送与接收“大同小异”之DCMTK fo-dicom mDCM

背景: 从DICOM网络传输一文开始,相继介绍了C-ECHO.C-FIND.C-STORE.C-MOVE等DIMSE-C服务的简单实现,博文中的代码给出的实例都是基于fo-dicom库来实现的,原因只有一个:基于C#的fo-dicom库具有高封装性.对于初学者来说实现大多数的DIMSE-C.DIMSE-N服务几乎都是"傻瓜式"操作--构造C-XXX-RQ.N-XXX-RQ然后绑定相应的OnResponseReceived处理函数即可.本博文希望在前几篇预热的基础上,对比DCMTK.fo

TeamTalk Android代码分析(业务流程篇)---消息发送和接收的整体逻辑说明

第一次纪录东西,也没有特别的顺序,想到哪里就随手画了一下,后续会继续整理- 6.2消息页面动作流程 6.2.1 消息页面初始化的总体思路 1.页面数据的填充更新直接由页面主线程从本地数据库请求 2.数据库数据的填充由后台线程异步方式从网络请求 3.前台线程每次按照18条记录读取数据库数据,后台线程按照每次18*3从网络请求数据 4.后台线程数据的请求由主线程满足一定的条件后发送总线事件,在 oneventbackgroudthread 中处理,具体条件(或的关系)如下: 1>第一次请求 2>本

Bluemix结合RabbitMq实现消息发送与接收实例

什么是RabbitMq? MQ全称为Message Queue, 消息队列(MQ)是一种应用程序对应用程序的通信方法.应用程序通过读写出入队列的消息(针对应用程序的数据)来通信,而无需专用连接来链接它们.消 息传递指的是程序之间通过在消息中发送数据进行通信,而不是通过直接调用彼此来通信,直接调用通常是用于诸如远程过程调用的技术.排队指的是应用程序通过 队列来通信.队列的使用除去了接收和发送应用程序同时执行的要求. 什么是Bluemix? BlueMix 是 IBM 基于 Cloud Foundr

cocos2dx 消息发送与接收

     cocos2dx有个自定义事件可以实现消息的发送和接收,叫EventListenerCustom.它是通过一个字符串来标识事件名称的.下面介绍下,我实现的这个消息的发送和接收.      首先,我们定义2个类,一个消息接收类,一个消息发送类.代码如下:       //消息接收 class cMsgReceiver { public: virtual void RecMsg(int iMsgId, void* pInfo, int iSize) { } }; //消息发送 class

ActiveMQ消息发送与接收

推荐文章:ActiveMQ讯息传送机制以及ACK机制 ActiveMQ发送消息 1:创建链接工厂ConnectionFactory 2:创建链接Connection 3:启动session 4:创建消息发送目的地 5:创建生产者 6:发送消息 消息发送类: package com.apt.study.util.activemq; import java.util.concurrent.atomic.AtomicInteger; import javax.jms.Connection; impor

linux 系统之间,网络编程,消息发送与接收

[email protected]:~/udp$ sudo apt-get update [email protected]:~/udp$ sudo apt-get install build-essential [email protected]:~/udp$ sudo apt-get install make [email protected]:~/udp$ ll -rw-rw-r-- 1 chunli chunli  279 May 15 10:36 makefile -rw-rw-r--

RocketMq入门之消息发送和接收

一.消息产生.发送 1 public class Producer { 2 public static void main(String[] args) throws MQClientException { 3 DefaultMQProducer producer = new DefaultMQProducer("rmq-group"); 4 producer.setNamesrvAddr("172.18.4.114:9876"); 5 producer.setIn

Windows消息拦截技术的应用

Windows消息拦截技术的应用 民航合肥空管中心 周毅 一.前 言 众所周知,Windows程式的运行是依靠发生的事件来驱动.换句话说,程式不断等待一个消息的发生,然后对这个消息的类型进行判断,再做适当的处理.处理完此次消息后又回到等待状态.从上面对Windows程式运行机制的分析不难发现,消息在用户与程式之间进行交流时起了一种中间“语言”的作用.在程式中接收和处理消息的主角是窗口,它通过消息泵接收消息,再通过一个窗口过程对消息进行相应的处理. 消息拦截的实现是在窗口过程处理消息之前拦截到消息