Thinking in MFC---消息机制1

Thinking in MFC---消息机制1

引言

在使用较长时间的MFC之后,感觉自己需要将零散的MFC知识整合一下,所以开始推出这个系列的博文,首先就从MFC经典的消息机制入手,来介绍MFC是怎么运作的。这篇主要介绍一下消息机制中几个基础概念。

这篇主要介绍消息如何路由到主窗口。

一、WinMain函数

写过win32程序,肯定只要我们要展示一个窗口,需要这个入口函数,这个就像控制台程序中main函数,是程序的入口。但是在MFC项目创建之后,我们在项目工程却找不到这个函数。

其实MFC已经将这个函数内部写好了,具体在下面这个文件中。

Microsoft Visual Studio10.0\VC\atlmfc\src\mfc\winmain.cpp

//

以下winmain函数中的内容,这些代码中的大部分我们暂时不做详细介绍,在之后的篇章中会来讨论这些代码。

 
ASSERT(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)
        {
            TRACE(traceAppMsg,0, "Warning: Destroying non-NULLm_pMainWnd\n");
            pThread->m_pMainWnd->DestroyWindow();
        }
        nReturnCode= pThread->ExitInstance();
        goto InitFailure;
    }
    nReturnCode =pThread->Run();
 
InitFailure:
#ifdef _DEBUG
    // Check for missing AfxLockTempMap calls
    if (AfxGetModuleThreadState()->m_nTempMapLock !=0)
    {
        TRACE(traceAppMsg,0, "Warning: Temp map lock count non-zero(%ld).\n",
            AfxGetModuleThreadState()->m_nTempMapLock);
    }
    AfxLockTempMaps();
    AfxUnlockTempMaps(-1);
#endif
 
    AfxWinTerm();
    return nReturnCode;

上诉代码中第27行代码,我们需要找的消息循环就在Run函数中,接下来就看一下这个Run函数。

二、消息路由到主窗口

这个类微软用来封装MFC的线程。

这里我们主要来讨论这个类中的run函数

virtual int CWinThread::Run()
{
    ASSERT_VALID(this);
    BOOL bIdle =TRUE;
    LONGlIdleCount = 0;
    //这里就是消息循环
    for (;;)
    {
         // 如果线程处于空闲状态,消息队列中也没有消息,做空闲处理
        while(bIdle && !::PeekMessage(&m_msgCur,NULL, NULL, NULL, PM_NOREMOVE))
        {
            // PeekMessage()用于检查消息队列是否为空,但并不处理消息
            if (!OnIdle(lIdleCount++))
                bIdle= FALSE;
        }
        do// 空闲处理接触,进行消息处理
        {
            if (!PumpMessage())// 消息处理,如果是WM_QUIT就退出消息循环
                return ExitInstance();
            if (IsIdleMessage(&m_msgCur))
            {
                bIdle= TRUE;
                lIdleCount= 0;
            }
        } while (::PeekMessage(&m_msgCur, NULL, NULL, NULL,PM_NOREMOVE));
    }
    ASSERT(FALSE);//
}

这里先说明,以上代码包括下面的代码都是笔者从网上找到的,所以对于其准确性不能够保证,但是作为介绍性说明,应该是没有问题的。

我们按照函数执行顺序往下看,首先我们发现了一个死循环,这个死循环便是我们要的消息循环。

接下来我们便是idle状态的处理,没有消息处理的时候,线程进入idle状态。其中PeekMessage,便是用来判断消息队列中是否有消息。

再往下看,我们会发现一个叫PumpMessage,这个函数便是这里的一个关键函数(从run函数的代码中看,好像也没有其对队消息进行处理的函数了)。

接下来就看一下PumpMessage函数的源码

BOOL CWinThread::PumpMessage()
{
    ASSERT_VALID(this);
    if (!::GetMessage(&m_msgCur, NULL, NULL, NULL))
        return FALSE;
    if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur))
    {
        ::TranslateMessage(&m_msgCur);
        ::DispatchMessage(&m_msgCur);
    }
    return TRUE;
}

按照执行顺序看,我们会注意到GetMessage,这个函数这里暂时不做介绍,就是从线程的消息队列中获取消息,并把消息内容放入一个结构的对象中(这里就放入msgCur)。

接下来便是判断消息是否是idle消息,如果不是就交给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 throughmain
    //   window'saccelerator table
    if (pMainWnd != NULL)
    {
        CWnd*pWnd = CWnd::FromHandle(pMsg->hwnd);
        if (pWnd->GetTopLevelParent() != pMainWnd)
            return pMainWnd->PreTranslateMessage(pMsg);
    }
 
    return FALSE;  // no special processing
}

我们按照函数的执行顺序来看,首先判断一下这个消息是否是发给窗口的。如果不是,那么就是一个线程消息,就交给DispatchThreadMessageEx(这里也暂时不介绍这个函数,不是这篇讨论的重点)。

接下面,我们获得主窗口对象,就把消息交给WalkPreTranslateTree处理。

继续探讨,我们有需要找到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 windowchecking
    // 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 bytarget window (eg: accelerators)
        }
        // got to hWndStop window without interest
        if (hWnd == hWndStop)
             break;
    }
    return FALSE; // no specialprocessing
}

略去一些空值的检查,我们看到消息最后是交给了主窗口的PreTranslateMessage函数处理了。接下来就交给主窗口进行消息处理了。

这里我们就大致了解了一个消息怎么路由到主窗口的流程。

三、总结

开始编程不久,便接触到了MFC。用了MFC写了自己第一个视窗程序,写了第一个游戏,在学校课题演示的时候也是喜欢用MFC做演示。

现在工作了,项目的界面部分也在使用MFC开发的,值得庆幸的是,现在微软对于MFC的界面做了很大的改善,想起刚开始不断重写控件类,那个真是记忆犹新。

在平时编程的时候,VS这个集成工具确实很方便。但现在想把一些MFC零零碎碎的知识整合起来。所以这个系列可能会深入地去介绍MFC。

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章