消息机制

http://carrolzswx.blog.163.com/blog/static/391728720077255266418/

上一次和MFC邂逅,我和她谈得很投机。[1]分别的时候,她还依依不舍地对我说:“别忘了给我消息哦。”忙了一个月了,直到这几天,我才想起她告诉我的那句话。今天终于忍不住要给她发条消息了,但是我该怎么发呢?完了,她都没有告诉我怎么发呢?看来又得自己努力了。

        我们都知道Windows应用程序是消息驱动(Message-Driven)的。而我们在编写应用程序的时候也是建立起消息映射表,并通过实现消息响应函数去处理用户操作产生的事件消息。关于消息驱动,有一个基本的概念要明确的,就是应用程序并不能直接得到来自用户操作的信息,而这些信息则是被操作系统拦截并封装成MSG类,然后再将信息发往相应的应用程序。        

        既然要发消息,那么就得先了解一下MSG的结构了。在WINUSER.H中,我们找到了MSG的定义:

        

typedef struct tagMSG {

    HWND        hwnd;

    UINT        message;

    WPARAM      wParam;

    LPARAM      lParam;

    DWORD       time;

    POINT       pt;

#ifdef _MAC

    DWORD       lPrivate;

#endif

} MSG, *PMSG, NEAR *NPMSG, FAR *LPMSG;

 

        在结构体tagMSG当中,hwnd是消息要发送到的窗口句柄(window handle);message是消息的标识符(message identifier),它是一个16位的无符号整数,用以表示消息的类型;而wParam和lParam则是消息本身的参数。

        有了对消息的初步了解后,接下去需要了解的是消息的发送或者称为消息路由(Message Routing)。操作系统会有两种方式发送消息,一种是将消息放入消息队列中,另外一种就是直接将消息发往相应的应用程序。

        首先分析第一种发送方式:操作系统维护着两种不同的消息队列:一个是系统消息队列,一个则是线程特定(thread-specific)消息队列(简称线程消息队列)。系统信息队列是各个GUI线程共享的,而线程特定信息队列则是每个GUI线程拥有一个。消息就由输入设备产生->系统消息队列->线程消息队列->相应的消息处理程序。而第二种方式则是越过了中间两个队列,直接传到了相应的消息处理程序。像窗口获得焦点(WM_SETFOCUS)这一类的信息都是以第二种方式发送的。

        当消息发送至线程消息队列以后,任务的重点就转到消息处理程序上了。消息的处理通常都是通过消息循环来进行的。以下是很简单的消息循环的例子,源自MSDN:

MSG msg;

BOOL bRet;

while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)

    if (bRet == -1)

    {

        // handle the error and possibly exit

    }

    else

    {

        TranslateMessage(&msg); 

        DispatchMessage(&msg); 

    }

}

        其中GetMessage方法的作用是从消息队列取得消息,并将消息复制到变量msg中,最后会将消息从队列中移走。GetMessage方法会有返回值,如果返回值为0(当取得的消息为WM_QUIT的时候),退出循环;如果返回值为-1,则说明取消息这一动作发生了异常,然后进行相应的处理;如果返回值为其他数值,则执行TranslateMessage和DispatchMessage方法。

        而在MFC当中,我们可以从CWinThread的Run方法中找到MFC使用消息循环处理消息的过程:

for (;;)

{

    // phase1: check to see if we can do idle work

    while (bIdle &&!::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE))

    {

        // call OnIdle while in bIdle state

    if (!OnIdle(lIdleCount++))

        bIdle = FALSE; // assume "no idle" state

    }

    // phase2: pump messages while available

    do

    {

        // pump message, but quit on WM_QUIT

    if (!PumpMessage())

        return ExitInstance();

    // reset "no idle" state after pumping "normal" message

    if (IsIdleMessage(&m_msgCur))

    {

        bIdle = TRUE;

        lIdleCount = 0;

    }

    } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));

}    

        从代码中,我们可以看到跟前面简单例子有两个不同的地方:第一是MFC使用了PeekMessage方法从消息队列中获得消息的方法,而不是直接使用GetMessage方法;第二就是使用了PumpMessage方法进行消息的处理。PeekMessage方法与GetMessage不同的地方就是:它从消息队列中得到消息,却不会将消息从队列中移走。使用PeekMessage方法也是因为它是一个具有异步行为的方法,如果消息队列中没有消息,它会立即返回,而GetMessage则不行,因为它的任务除了获得消息之外,还要从消息队列中将获得的消息移走,因此,一旦消息队列中没有消息,GetMessage方法就会被阻塞了,使得线程处于睡眠状态,这样就不会出现所谓的Idle状态,也就不会去执行OnIdle方法了。接下来,我们将注意力转到PumpMessage方法中:

BOOL CWinThread::PumpMessage()

{

    ASSERT_VALID(this);

    if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))

    {

        #ifdef _DEBUG

        if (afxTraceFlags & traceAppMsg)

        TRACE0("CWinThread::PumpMessage - Received WM_QUIT. ");

        m_nDisablePumpCount++; // application must die

        // Note: prevents calling message loop things in

        //       'ExitInstance'will never be decremented

        #endif

    return FALSE;

    }

    #ifdef _DEBUG

    if (m_nDisablePumpCount != 0)

    {

        TRACE0("Error: CWinThread::PumpMessage called when not permitted. ");

    ASSERT(FALSE);

    }

    #endif

    #ifdef _DEBUG

    if (afxTraceFlags & traceAppMsg)

        _AfxTraceMsg(_T("PumpMessage"), &m_msgCur);

    #endif

    // process this message

    if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))

    {

    ::TranslateMessage(&m_msgCur);

    ::DispatchMessage(&m_msgCur);

    }

    return TRUE;

}

         从代码中,我们可以知道,在PumpMessage方法中,通过调用GetMessage方法从消息队列中取走消息,然后根据其返回值来决定是否返回,这跟前面的简单例子是很类似的。如果GetMessage方法返回的是0或者-1都会返回FALSE,方法结束;如果返回的是其他正整数,那么将会执行最后的一个条件语句,在那里我们又见到了熟悉的TranslateMessage和DispatchMessage方法。通过这样一层一层的阅读源代码,我们可以发现MFC消息处理流程的主体与前面的简单例子是很类似的,只是增加了一些相应的扩展,使得消息的处理更灵活。

        折腾了半天,终于对消息有了初步的了解。虽然MFC消息处理的流程并没有很特别的地方,但是其OnIdle方法以及PreTranslateMessage方法都是需要仔细研究的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值