2006
08-12

MFC的WinMain


  使用MFC编程的程序员刚开始都会提出这样一个问题:我的程序是从哪儿开始执行的?回答是:从WinMain()开始执行的。提出这样的问题是由于在他们所编写的MFC应用中看不到WinMain()函数。这个函数是隐藏在MFC框架中,MFC的设计者将它作得很通用(这主要得益于Window的消息驱动的编程机制,使得作一个通用的WinMain()很容易),因此在一般情况下,无需更改WinMain()的代码,MFC的设计者也不提倡程序员修改WinMain()的代码。在MFC中,实际实现WinMain()的代码是AfxWinMain()函数(根据其前缀Afx就知道这是一个全局的MFC函数)。


  一个Win32应用程序(或进程)是由一个或多个并发的线程组成的,其中第一个启动的线程称为主线程,在Window下,一般将线程分成两大类,界面线程和工作线程,工作线程就是一般的线程,它没有窗口,没有消息队列等,界面线程拥有一个或多个窗口,拥有一个消息队列和其他专属于界面线程的元素。在讨论AfxWinMain()之前,首先要简略提一下MFC中的两个重要的类,CWinThread和CWinApp,CWinThread是用来封装界面线程的类,CWinApp是从CWinThread派生而来的。在CWinThread中,有两个很重要的虚拟函数InitInstance()和ExitInistance(),MFC的程序员应该对这两个函数应该很熟悉。在CWinApp中,增加了另外一个虚拟函数InitApplication(),讨论AfxWinMain()的主要目的是看这些函数是如何被调用的。


  AfxWinMain()的代码如下:


  int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,


  LPTSTR lpCmdLine, int nCmdShow)


  {


   ASSERT(hPrevInstance == NULL); file://在win32下,hPrevInstance始终为NULL


   int nReturnCode = -1;


   CWinThread* pThread = AfxGetThread();


   CWinApp* pApp = AfxGetApp();


   // AFX internal initialization


   if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow))


    goto InitFailure;


    // App global initializations (rare)


   if (pApp != NULL && !pApp->InitApplication())


    goto InitFailure;


    // Perform specific initializations


   if (!pThread->InitInstance())


   {


    if (pThread->m_pMainWnd != NULL)


    {


     TRACE0(“Warning: Destroying non-NULL m_pMainWnd “);


     pThread->m_pMainWnd->DestroyWindow();


    }


    nReturnCode = pThread->ExitInstance();


    goto InitFailure;


    }


   nReturnCode = pThread->Run();


   InitFailure:


   AfxWinTerm();


  return nReturnCode;


  }


  在上面的代码中,AfxGetThread()返回的是当前界面线程对象的指针,AfxGetApp()返回的是应用程序对象的指针,如果该应用程序(或进程)只有一个界面线程在运行,那么这两者返回的都是一个全局的应用程序对象指针,这个全局的应用程序对象就是MFC应用框架所默认的theApp对象(每次使用AppWizard生成一个SDI或MDI应用程序时,AppWizard都会添加CYourApp theApp这条语句,AfxGetApp()返回的就是这个theApp的地址)。


CWinApp::InitApplication(), CWinThread::InitInstance(), CWinThread::ExitInstance()是如何被调用的,从上面的代码一看就知,我不再赘述。下面我们把焦点放在CWinThread::Run()上。


MFC的控制中心?D?DCWinThread::Run()


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


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


  CWinThread::Run()的结构


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


  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()的核心?D?DCWinThread::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的消息分发模式。


<三>MFC的特色?D?DPreTranslateMessage()


  经过层层扒皮,终于找到了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()处理,在某些情况下,要仔细处理,以免漏掉消息。


留下一个回复