您的位置:首页 > 编程语言 > C语言/C++

CWinThread::Run() 简单理解

2016-10-12 10:16 190 查看
MFC的控制中心――CWinThread::Run()

 

  说CWinThread::
Run()是MFC的控制中心,一点也没有夸大。在MFC中,所有来自于消息队列的消息的分派都是在CWinThread::Run()函数中完成的,同 AfxWinMain()一样,这个函数也是对程序员是不可见的,其道理同AfxWinMain()的一样。

 

  首先要提的一点是,对每 条从消息队列取出来的消息,MFC根据消息的类型,按照某个特定的模式进行分发处理,这个分发模式是MFC自己定义的。固定的消息分发流程和在这个流程中 的可动态改变其行为的虚拟函数就构成了MFC的消息分发模式。应用程序可以通过重载这些虚拟函数,来局部定制自己的的消息分发模式。正是通过这些虚拟函数,MFC为应用程序提供了足够的灵活性。下面讨论的所有代码都来自于MFC源代码中的../MFC/SRC/threadcore.cpp文件,它们都是CWinThread 的成员。

 

但是对话框程序没有对CWinThread::Run()的调用。新建一个SDI的项目,打断点,看看CWinThread::Run()被调用时候的栈:

CWinThread::Run() line 468

CWinApp::Run() line 400

AfxWinMain(HINSTANCE__ * 0x00400000,
HINSTANCE__ * 0x00000000, char * 0x00151f2b, int 1) line 49 + 11 bytes

WinMain(HINSTANCE__ * 0x00400000,
HINSTANCE__ * 0x00000000, char * 0x00151f2b, int 1) line 30

WinMainCRTStartup() line 330
+ 54 bytes

KERNEL32! 7c816fd7()

很明显SDI和dialog在AfxWinMain函数中走的逻辑就已经不同了。

 

 

 

  CWinThread::Run()的结构

 

  CWinThread::Run()的代码如下:

// main running routine until
thread exits

int CWinThread::Run()

{

       ASSERT_VALID(this);

 

       //
for tracking the idle time state

       BOOL
bIdle = TRUE;

       LONG
lIdleCount = 0;

 

       //
acquire and dispatch messages until a WM_QUIT message is received.

       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));

       }

 

       ASSERT(FALSE);  //
not reachable

}

 

CWinThread::Run()的处理过程如下:

 

  先根据空闲标志以及消息队列是否为空这两个条件判断当前线程是否处于空闲状态(这个“空闲”的含义同操作系统的含义不同,是MFC自己所谓的“空闲”),如果是,就调用CWinThread::OnIdle(),这也是我们比较熟悉的一个虚拟函数。

 

  如果不是,从消息队列中取出消息,进行处理,直到消息队列为空。

 

   在这里,我们发现,MFC不是调用GetMessage()从线程消息队列中取消息,而是调用PeekMessage()。其原因在于,GetMessage()是一个具有同步行为的函数,如果消息队列中没有消息,GetMessage()会一直阻塞,使得线程处于睡眠状态,直到消息队列 中有一条或多条消息,操作系统才会唤醒该线程,GetMessage()才会返回,如果线程处于睡眠状态了,就不会使线程具有MFC所谓的“空闲”状态 了;而PeekMessage()则是一个具有异步行为的函数,如果消息队列中没有消息,它马上返回0,不会导致线程处于睡眠状态。

 

   在上面的代码中,有两个函数值得探讨,一个是空闲处理函数OnIdle(),另外一个是消息分发处理函数PumpMessage()。不要忽视 CWinThread的OnIdle()函数,它作了很多有意义的事情。下面讨论PumpMessage(),OnIdle()将在后面的章节里讨论。

 

  CWinThread::Run()的核心――CWinThread::PumpMessage()

 

  标题强调了PumpMessage()的重要性,Run()是MFC的控制中心,而PumpMessage()又是Run()的核心,所以从MFC的真正控制中心是PumpMessage()。PumpMessage()的代码极其简单:

 

  BOOL
CWinThread::PumpMessage()

  {

   ASSERT_VALID(this);

 

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

    return
FALSE;

   //
process this message

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

   {

    ::TranslateMessage(&m_msgCur);

    ::DispatchMessage(&m_msgCur);

   }

   return
TRUE;

  }

 

   首先,PumpMessage()调用GetMessage()从消息队列中取一条消息,由于PumpMessage()是在消息队列中有消息的时候才 被调用的,所以GetMessage()会马上返回,根据其返回值,判断当前取出的消息是不是WM_QUIT【这个描述好像有些问题】消息(这个消息一般对是通过调用 PostQuitMessage()放入线程消息队列的),如果是,就返回FALSE,CWinThread::Run()该退出了, CWinThread::Run()直接调用CWinThread::ExitInstance()退出应用程序。在GetMessage()的后面是我 们所熟悉的TranslateMessage()和DispatchMessage()函数。

 

可以看出,是否调用TranslateMessage()和DispatchMessage()是由一个名称为PreTranslateMessage()函数的返回值决定的,如果该函数返回TRUE,则不会把该消息分发给窗口函数处理。

 

  就我个人观点而言,正是有了这个PreTranslateMessage(),才使得MFC能够灵活的控制消息的分发模式,可以说,PreTranslateMessage()就是MFC的消息分发模式。

 

 

 <</SPAN>三>MFC的特色――PreTranslateMessage()

 

   经过层层扒皮,终于找到了CWinThread::Run()最具特色的地方,这就是PreTranslateMessage()函数。同前面使用SDK编写的显示”Hello,
world!”程序的消息循环不同的地方在于,MFC多了这个PreTranslateMessage(),PreTranslateMessage() 最先获得了应用程序的消息处理权!下面我们对PreTranslateMessage()进行剥皮式分析。同前面一样,首先看看实际的 PreTranslateMessage()的代码:

BOOL CWinThread::PreTranslateMessage(MSG*
pMsg)

{

       ASSERT_VALID(this);

 

       //
if this is a thread-message, short-circuit this function

       if
(pMsg->hwnd == NULL && DispatchThreadMessageEx(pMsg))

              return
TRUE;

 

       //
walk from target to main window

       CWnd*
pMainWnd = AfxGetMainWnd();

       if
(CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(), pMsg))

              return
TRUE;

 

       //
in case of modeless dialogs, last chance route through main

       //   window's
accelerator table

       if
(pMainWnd != NULL)

       {

               CWnd*
pWnd = CWnd::FromHandle(pMsg->hwnd);

               if
(pWnd->GetTopLevelParent() != pMainWnd)

                     return
pMainWnd->PreTranslateMessage(pMsg);

       }

 

       return
FALSE;   // no special processing

}

 

  PreTranslateMessage()的处理过程如下:

 

  首先判断该消息是否是一个线程消息(消息的窗口句柄为空的消息),如果是,交给DispatchThreadMessageEx()处理。我们暂时不管DispatchThreadMessageEx(),它不是我们讨论的重点。

  调用CWnd::WalkPreTranslateTree()对该消息进行处理,注意该函数的一个参数是线程主窗口的句柄,这是PreTranslateMessage()的核心代码,在后面会对这个函数进行详细的分析。

  

  下面详细讨论一下CWnd::WalkPreTranslateTree()函数,它的代码很简单:

  BOOL
PASCAL CWnd::WalkPreTranslateTree(HWND hWndStop, MSG* pMsg)

  {

   ASSERT(hWndStop
== NULL || ::IsWindow(hWndStop));

   ASSERT(pMsg
!= NULL);

   //
walk from the target window up to the hWndStop window checking

   //
if any window wants to translate this message

   for
(HWND hWnd = pMsg->hwnd; hWnd != NULL; hWnd = ::GetParent(hWnd))

   {

    CWnd*
pWnd = CWnd::FromHandlePermanent(hWnd);

    if
(pWnd != NULL)

    {

     //
target window is a C++ window

     if
(pWnd->PreTranslateMessage(pMsg))

      return
TRUE; // trapped by target window (eg: accelerators)

    }

    //
got to hWndStop window without interest

    if
(hWnd == hWndStop)

    break;

   }

   return
FALSE; // no special processing

  }

 

CWnd:: WalkPreTranslateTree()的所使用的策略很简单,拥有该消息的窗口最先获得该消息的处理权,如果它不想对该消息进行处理(该窗口对象 的PreTranslateMessage()函数返回FALSE),就将处理权交给它的父亲窗口,如此向树的根部遍历,直到遇到hWndStop(在 CWinThread::PreTranslateMessage()中,hWndStop表示的是线程主窗口的句柄)。记住这个消息处理权的传递方向, 是由树的某个一般节点或叶子节点向树的根部传递!

 

  小结:

  下面对这一章作一个小结。

  MFC消息控制流最具特色的地方是CWnd类的虚拟函数PreTranslateMessage(),通过重载这个函数,我们可以改变MFC的消息控制流程,甚至可以作一个全新的控制流出来,在下面的一章会对MFC的实现作详细介绍。

  只有穿过消息队列的消息才受PreTranslateMessage()影响,采用SendMessage()或其他类似的方式向窗口直接发送的而不经过消息队列的消息根本不会理睬PreTranslateMessage()的存在

  传给PreTranslateMessage()的消息是未经翻译过的消息,它没有经过TranslateMessage()处理,在某些情况下,要仔细处理,以免漏掉消息

转载!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  c++ windows mfc