深入探讨MFC消息循环和消息泵

来源:互联网 发布:乐敦养润水副作用知乎 编辑:程序博客网 时间:2024/06/05 06:49
//深入探讨MFC消息循环和消息泵


/*首先,应该清楚MFC的消息循环(::GetMessage,::PeekMessage),
消息泵(CWinThread::PumpMessage)和MFC的消息在窗口之间的路由是两件不同的事情。
在MFC的应用程序中(应用程序类基于CWinThread继承),必须要有一个消息循环,他的作用是从应用程序的消息队列中读取消息,并把它派送出去(::DispatchMessage)。
而消息路由是指消息派送出去之后,系统(USER32.DLL)把消息投递到哪个窗口,以及以后消息在窗口之间的传递是怎样的。

消息分为队列消息(进入线程的消息队列)和非队列消息(不进入线程的消息队列)。
对于队列消息,最常见的是鼠标和键盘触发的消息,例如WM_MOUSERMOVE,WM_CHAR等消息;还有例如:WM_PAINT、WM_TIMER和WM_QUIT。当鼠标、键盘事件被触发后,相应的鼠标或键盘驱动程序就会把这些事件转换成相应的消息,然后输送到系统消息队列,由Windows系统负责把消息加入到相应线程的消息队列中,于是就有了消息循环(从消息队列中读取并派送消息)。
还有一种是非队列消息,他绕过系统队列和消息队列,直接将消息发送到窗口过程。例如,当用户激活一个窗口系统发送WM_ACTIVATE,WM_SETFOCUS, andWM_SETCURSOR。创建窗口时发送WM_CREATE消息。在后面你将看到,MS这么设计是很有道理的,以及他的整套实现机制。
*/

//这里讲述MFC的消息循环,消息泵。先看看程序启动时,怎么进入消息循环的:

_tWinMain ->AfxWinMain ->AfxWinInit->CWinThread::InitApplication->CWinThread::InitInstance->CWinThread::Run

非对话框程序的消息循环的事情都从这CWinThread的一Run开始...

第一部分:非对话框程序的消息循环机制。

//thrdcore.cpp// main running routine untilthread exitsint CWinThread::Run(){    ASSERT_VALID(this);    // for tracking the idle timestate    BOOL bIdle = TRUE;    LONG lIdleCount = 0;// acquire and dispatch messagesuntil a WM_QUIT message is received.    for (;;)    {// phase1: checkto see if we can do idle work    while (bIdle &&!::PeekMessage(&m_msgCur, NULL, NULL, NULL,PM_NOREMOVE))    {        // call OnIdle while in bIdlestate        if(!OnIdle(lIdleCount++))            bIdle =FALSE; // assume "no idle" state        }// phase2: pump messages whileavailable        do        {            // pump message, but quit onWM_QUIT            if (!PumpMessage())            returnExitInstance();            // reset "no idle" state afterpumping "normal" message            if(IsIdleMessage(&m_msgCur))            {                bIdle =TRUE;                lIdleCount =0;            }        } while(::PeekMessage(&m_msgCur, NULL, NULL, NULL,PM_NOREMOVE));    }    //无限循环,退出条件是收到WM_QUIT消息。    ASSERT(FALSE); // not reachable}


//这是一个无限循环,他的退出条件是收到WM_QUIT消息:

    if (!PumpMessage())        returnExitInstance();


/*在PumpMessage中,如果收到WM_QUIT消息,那么返回FALSE,所以ExitInstance()函数执行,跳出循环,返回程序的退出代码。
所以,一个程序要退出,只用在代码中调用函数*/
VOID PostQuitMessage( int nExitCode)

//指定退出代码nExitCode就可以退出程序。

//下面讨论一下这个函数Run的流程,分两步:

/*
1,第一个内循环phase1。bIdle代表程序是否空闲。
他的意思就是,如果程序是空闲并且消息队列中没有要处理的消息,那么调用虚函数OnIdle进行空闲处理。
在这个处理中将更新UI界面(比如工具栏按钮的enable和disable状态),
删除临时对象(比如用FromHandle得到的对象指针。由于这个原因,在函数之间传递由FromHandle得到的对象指针是不安全的,因为他没有持久性)。
OnIdle是可以重载的,你可以重载他并返回TRUE使消息循环继续处于空闲状态。
*/
/*
NOTE:MS用临时对象是出于效率上的考虑,使内存有效利用,并能够在空闲时自动撤销资源。
关于由句柄转换成对象,可以有若干种方法。
一般是先申明一个对象obj,然后使用obj.Attatch来和一个句柄绑定。
这样产生的对象是永久的,你必须用obj.Detach来释放对象。
*/
/*
2,第二个内循环phase2。
在这个循环内先启动消息泵(PumpMessage),如果不是WM_QUIT消息,消息泵将消息发送出去(::DispatchMessage)。
消息的目的地是消息结构中的hwnd字段所对应的窗口。
*/

//thrdcore.cppBOOLCWinThread::PumpMessage(){    ASSERT_VALID(this);//如果是WM_QUIT就退出函数(returnFALSE),这将导致程序结束.    if(!::GetMessage(&m_msgCur, NULL, NULL, NULL)){#ifdef _DEBUG    if (afxTraceFlags& traceAppMsg)    TRACE0("CWinThread::PumpMessage - Received WM_QUIT.\n");    m_nDisablePumpCount++; //application must die    // Note:prevents calling message loop things in 'ExitInstance'    // willnever be decremented#endif    return FALSE;}#ifdef _DEBUG    if (m_nDisablePumpCount != 0)    {        TRACE0("Error:CWinThread::PumpMessage called when not permitted.\n");        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;}

/*
在这一步有一个特别重要的函数大家一定认识:PreTranslateMessage。
这个函数在::DispatchMessage发送消息到窗口之前,进行对消息的预处理。
PreTranslateMessage函数是CWinThread的成员函数,大家重载的时候都是在View类或者主窗口类中,那么,它是怎么进入别的类的呢?
代码如下:
*/

//thrdcore.cppBOOL CWinThread::PreTranslateMessage(MSG* pMsg){    ASSERT_VALID(this);        // 如果是线程消息,那么将会调用线程消息的处理函数    if (pMsg->hwnd == NULL&&DispatchThreadMessageEx(pMsg))        return TRUE;        // walk from target to mainwindow    CWnd* pMainWnd = AfxGetMainWnd();    if(CWnd::WalkPreTranslateTree(pMainWnd->GetSafeHwnd(),pMsg))        return TRUE;        // in case of modeless dialogs,last chance route through main    //   window's acceleratortable    if (pMainWnd != NULL)    {        CWnd* pWnd =CWnd::FromHandle(pMsg->hwnd);        if(pWnd->GetTopLevelParent() != pMainWnd)            returnpMainWnd->PreTranslateMessage(pMsg);    }        returnFALSE;   // no specialprocessing}



由上面这个函数可以看出:
第一,如果(pMsg->hwnd ==NULL),说明这是一个线程消息。
    调用CWinThread::DispatchThreadMessageEx到消息映射表找到消息入口,然后调用消息处理函数。

NOTE:一般用PostThreadMessage函数发送
线程之间的消息,他和窗口消息不同,需要指定线程id,
    消息激被系统放入到目标线程的消息队列中;用ON_THREAD_MESSAGE(message,memberFxn)宏可以映射线程消息和他的处理函数。
    这个宏必须在应用程序类(CWinThread继承)中,因为只有应用程序类才处理线程消息。
    如果你在别的类(比如视图类)中用这个宏,线程消息的消息处理函数将得不到线程消息。

第二,消息的目标窗口的PreTranslateMessage函数首先得到消息处理权,
    如果函数返回FALSE,那么他的父窗口将得到消息的处理权,直到主窗口;
    如果函数返回TRUE(表示消息已经被处理了),那么就不需要调用父类的PreTranslateMessage函数。
    这样,保证了消息的目标窗口以及他的父窗口都可以有机会调用PreTranslateMessage
    --在消息发送到窗口之前进行预处理(如果自己处理完然后返回FALSE的话-_-b),
    如果你想要消息不传递给父类进行处理的话,返回TRUE就行了。


第三,如果消息的目标窗口和主窗口没有父子关系,那么再调用主窗口的PreTranslateMessage函数。
    为什么这样?由第二步知道,一个窗口的父窗口不是主窗口的话,尽管它的PreTranslateMessage返回FALSE
    主窗口也没有机会调用PreTranslateMessage函数。我们知道,加速键的转换一般在框架窗口的PreTranslateMessage函数中。
    我找遍了MFC中关于加速键转换的处理,只有CFrameWnd,CMDIFrameWndCMDIChildWnd等窗口类有。
    所以,第三步的意思是,如果消息的目标窗口(他的父窗口不是主窗口,比如一个这样的非模式对话框)
    使消息的预处理继续漫游的话(他的PreTranslateMessage返回FALSE)
    那么给一次机会给主窗口调用PreTranslateMessage(万一他是某个加速键消息呢?)
    这样能够保证在有非模式对话框的情况下还能保证主窗口的加速键好使。

我做了一个小例子
    在对话框类的PreTranslateMessage中,返回FALSE
    在主窗口显示这个非模式对话框,在对话框拥有焦点的时候,仍然能够激活主窗口的快捷键。

总之,整个框架就是让每个消息的目标窗口(包括他的父窗口)都有机会参与消息到来之前的处理。呵呵~

至此,非对话框的消息循环和消息泵的机制就差不多了。
这个机制在一个无限循环中,不断地从消息队列中获取消息,并且保证了程序的线程消息能够得到机会处理,
窗口消息在预处理之后被发送到相应的窗口处理过程。

那么,还有一点疑问,为什么要一会儿调用::PeekMessage,一会儿调用::GetMessage呢,他们有什么区别?

NOTE:一般来说,
GetMessage被设计用来高效地从消息队列获取消息。
   
如果队列中没有消息,那么函数GetMessage将导致线程休眠(让出CPU时间)。
   
而PeekMessage是判断消息队列中如果没有消息,它马上返回0,不会导致线程处于睡眠状态。

在上面的phase1第一个内循环中用到了PeekMessage
    它的参数PM_NOREMOVE表示并不从消息队列中移走消息,而是一个
检测查询
    如果消息队列中没有消息他立刻返回0,如果这时线程空闲的话将会引起消息循环调用OnIdle处理过程(上面讲到了这个函数的重要性)
    如果将::PeekMessage改成::GetMessage(***),
那么如果消息队列中没有消息,线程将休眠,直到线程下一次获得CPU时间并且有消息出现才可能继续执行,
    这样,消息循环的空闲时间没有得到应用,OnIdle也将得不到执行。这就是为什么既要用::PeekMessage(查询),又要用::GetMessage(做实际的工作)的缘故。

第二部分: 对话框程序的消息循环机制

基于对话框的MFC工程和上面的消息循环机制不一样。实际上MFC的对话框工程程序就是模式对话框。
他和上面讲到的非对话框程序的不同之处,主要在于应用程序对象的InitInstance()不一样。

//dlg_5Dlg.cppBOOL CDlg_5App::InitInstance(){    AfxEnableControlContainer();#ifdef _AFXDLL    Enable3dControls();   // Call this when using MFC in a shared DLL#else    Enable3dControlsStatic(); // Call this when linking to MFCstatically#endif    CDlg_5Dlgdlg; //定义一个对话框对象    m_pMainWnd = &dlg;    int nResponse = dlg.DoModal(); //对话框的消息循环在这里面开始    if (nResponse == IDOK)    {    // TODO: Place code here tohandle when the dialog is    // dismissed with OK    }    else if (nResponse == IDCANCEL)    {    // TODO: Place code here tohandle when the dialog is    // dismissed withCancel    }    // Since the dialog has beenclosed, return FALSE so that we exit the    // application, rather than start the application's messagepump.    return FALSE;}


NOTE:InitInstance函数返回FALSE,由最上面程序启动流程可以看出,CWinThread::Run是不会得到执行的。
也就是说,上面第一部分说的消息循环在对话框中是不能执行的。
实际上,对话框也有消息循环,她的消息循环在CDialog::DoModal()虚函数中的一个RunModalLoop函数中。

这个函数的实现体在CWnd类中:

int CWnd::RunModalLoop(DWORD dwFlags){    ASSERT(::IsWindow(m_hWnd)); // window must be created    ASSERT(!(m_nFlags & WF_MODALLOOP)); // window mustnot already be in modal state// for tracking the idle timestate    BOOL bIdle = TRUE;    LONG lIdleCount = 0;    BOOL bShowIdle = (dwFlags & MLF_SHOWONIDLE)&& !(GetStyle() &WS_VISIBLE);    HWND hWndParent = ::GetParent(m_hWnd);    m_nFlags |= (WF_MODALLOOP|WF_CONTINUEMODAL);    MSG* pMsg =&AfxGetThread()->m_msgCur;// acquire and dispatch messagesuntil the modal state is done    for (;;)    {        ASSERT(ContinueModal());//phase1: check to see if we can do idle work        while (bIdle&& !::PeekMessage(pMsg, NULL, NULL, NULL, PM_NOREMOVE))        {            ASSERT(ContinueModal());            // show thedialog when the message queue goes idle            if(bShowIdle)            {                ShowWindow(SW_SHOWNORMAL);                UpdateWindow();                bShowIdle = FALSE;            }            // callOnIdle while in bIdle state            if(!(dwFlags & MLF_NOIDLEMSG)&& hWndParent != NULL&& lIdleCount == 0)            {                // send WM_ENTERIDLE to the parent                ::SendMessage(hWndParent, WM_ENTERIDLE, MSGF_DIALOGBOX,(LPARAM)m_hWnd);            }            if ((dwFlags& MLF_NOKICKIDLE) ||            !SendMessage(WM_KICKIDLE, MSGF_DIALOGBOX, lIdleCount++))            {                // stop idle processing next time                bIdle = FALSE;            }        }    // phase2: pump messages whileavailable        do        {            ASSERT(ContinueModal());            // pumpmessage, but quit on WM_QUIT            //PumpMessage(消息泵)的实现和上面讲的差不多。都是派送消息到窗口。            if(!AfxGetThread()->PumpMessage())            {                AfxPostQuitMessage(0);                return -1;            }            // show thewindow when certain special messages rec'd            if(bShowIdle &&                (pMsg->message == 0x118 ||pMsg->message == WM_SYSKEYDOWN))            {                ShowWindow(SW_SHOWNORMAL);                UpdateWindow();                bShowIdle = FALSE;            }            if(!ContinueModal())                goto ExitModal;            // reset "noidle" state after pumping "normal" message            if(AfxGetThread()->IsIdleMessage(pMsg))            {                bIdle = TRUE;                lIdleCount = 0;            }        } while (::PeekMessage(pMsg,NULL, NULL, NULL, PM_NOREMOVE));    } //无限循环    ExitModal:    m_nFlags &=~(WF_MODALLOOP|WF_CONTINUEMODAL);    return m_nModalResult;}


先说说怎么退出这个无限循环,在代码中:

if (!ContinueModal())    goto ExitModal;


决定是否退出循环,消息循环函数返回也就是快要结束结束程序了。

BOOL CWnd::ContinueModal(){     return m_nFlags & WF_CONTINUEMODAL;}


NOTE:CWnd::ContinueModal()函数检查对话框是否继续模式。
    返回TRUE,表示现在是模式的;
    返回FALSE,表示对话框已经不是模式(将要结束)

如果要结束对话框,在内部最终会调用函数CWnd::EndModalLoop
它取消m_nFlags的模式标志(消息循环中的ContinueModal函数将返回FALSE,消息循环将结束,程序将退出)
然后激发消息循环读取消息。也就是说,结束模式对话框是一个标志,改变这个标志就可以了。
他的代码是:

//wincore.cppvoid CWnd::EndModalLoop(intnResult){    ASSERT(::IsWindow(m_hWnd));    // this result will be returnedfrom CWnd::RunModalLoop    m_nModalResult = nResult;    // make sure a message goes throughto exit the modal loop    if (m_nFlags & WF_CONTINUEMODAL)    {        m_nFlags &=~WF_CONTINUEMODAL;        PostMessage(WM_NULL);    }}


NOTE:PostMessage(NULL)是有用的。如果消息队列中没有消息的话,
可能消息循环中的ContinueModal()不会马上执行,发送一个空消息是激发消息循环马上工作。

下面说一下CWnd::RunModalLoop函数中的消息循环究竟干了些什么事情:
1,第一个内循环。首先从消息队列中查询消息,如果对话框空闲,而且消息队列中没有消息,
    他做三件事情,大家应到都能从字面上明白什么意思。
   
最重要的是发送WM_KICKIDLE消息。为什么呢?
    第一部分讲到了,
非对话框程序用OnIdle来更新用户界面(UI),比如工具栏,状态栏。
    那么,如果对话框中也有工具栏和状态栏呢,在哪里更新(网上有很多这样的程序)
    可以处理WM_KICKIDLE消息:

LRESULT CDlg_5Dlg::OnKickIdle(WPARAM w,LPARAM l){    //调用CWnd::UpdateDialogControls更新用户界面    UpdateDialogControls(this, TRUE);    return 0;}

NOTE: CWnd::UpdateDialog函数发送CN_UPDATE_COMMAND_UI消息给所有的用户界面对话框控件。

2,第二个内循环。最重要的还是PumpMessage派送消息到目标窗口。
    其他的,像第二个if语句,0x118消息好像是WM_SYSTIMER消息(系统用来通知光标跳动的一个消息)
    也就是说,如果消息为WM_SYSTIMER或者WM_SYSKEYDOWN,并且空闲显示标志为真的话,就显示窗口并通知窗口立刻重绘。

总之,对话框的消息循环机制和非对话框(比如SDI,MDI)还是类似的,仅仅侧重点不同。
    模式对话框是模式显示,自然有他的特点。
    下面部分讨论一下模式对话框和非模式对话框的区别。
    因为模式对话框有自己的特殊消息循环;而非模式对话框,共用程序的消息循环,和普通的窗口已经没有什么大的区别了。


第三部分:模式对话框和非模式对话框的区别

这个话题已经有很多人讨论,我说说我所理解的意思。
MFC框架中,一个对话框对象DoModal一下就能产生一个模式对话框,Create一下就能产生一个非模式对话框。
实际上,无论是模式对话框还是非模式对话框,在MFC内部都是调用::CreateDialogIndirect(***)函数来创建非模式对话框。
    只是模式对话框作了更多的工作,包括使父窗口无效,然后进入自己的消息循环等等。
    ::CreateDialogIndirect(***)函数最终调用CreateWindowEx函数通知系统创建窗体并返回句柄,他内部没有实现自己的消息循环。
    非模式对话框创建之后立即返回,并且和主程序共用一个消息循环。
    非模式对话框要等对话框结束之后才返回,自己有消息循环。比如下面的代码:

    CMyDlg* pdlg = new CMyDlg;
    pdlg ->Create(IDD_DIALOG1);
    pdlg->ShowWindow(SW_SHOW);
    MessageBox("abc");

非模式对话框和消息框MessageBox几乎是同时弹出来。
而如果将Create改成DoModal,那么,只能弹出模式对话框,在关闭了对话框之后(模式对话框自己的消息循环结束),消息框才弹出来。

NOTE:可以在模式对话框中调用GetParent()->EnableWindow(true);
这样,主窗口的菜单,工具栏又激活了,能用了。
MFC使用非模式对话框来模拟模式对话框,而在win32SDK程序中,模式对话框激发他的父窗口Enable操作是没有效果的。

关于消息循环总结:

1,我们站在一个什么高度看消息循环?消息循环其实没有什么深奥的道理。
    如果一个邮递员要不断在一个城市中送信,我们要求他做什么?要求他来回跑,但他一次只能在一个地方出现。
    如果我们的
应用程序只有一个线程的话,我们要他不断地为窗口传递消息,我们怎么做?
    在一个循环中不断的检测消息,并将他发送到适当的窗口。
窗口可以有很多个,但消息循环只有一个,而且每时每刻最多只有一个地方在执行代码。
    为什么?看第二点。

2,因为是单线程的(程序进程启动的时候,只有而且有一个线程,我们称他为主线程),所以就像邮递员一样,每次只能在某一个地方干活。
什么意思呢?举个例子,用::DiapatchMessage派送消息,在窗口处理过程(WinProc,窗口函数)返回之前,他是阻塞的,不会立即返回,
也就是消息循环此时不能再从消息队列中读取消息,直到::DispatchMessage返回。如果你在窗口函数中执行一个死循环操作,
就算你用PostQuitMessage函数退出,程序也会down掉。
while(1){    PostQuitMessage(0); //程序照样down.}

所以,当窗口函数处理没有返回的时候,消息循环是不会从消息队列中读取消息的。
这也是为什么在
模式对话框中要自己用无限循环来继续消息循环,因为这个无限循环阻塞了原来的消息循环,
所以,在这个无限循环中要用GetMessage,PeekMessage,DispatchMessage来从消息队列中读取消息并派送消息了。

要不然程序就不会响应了,这不是我们所希望的。

所以说,消息循环放在程序的什么的地方都基本上是过的去的,比如放在DLL里面。

但是,最好在任何时候,只有一个消息循环在工作(其他的都被阻塞了)

然后,我们要作好的一件事情,就是怎么从消息循环中退出!

当然用WM_QUIT是可以拉~(PostThreadMessage也是个好主意)

这个消息循环退出后,可能程序退出,也可能会激活另外一个被阻塞的消息循环,程序继续运行。

这要看你怎么想,怎么去做。最后一个消息循环结束的时候,也许就是程序快结束的时候,因为主线程的执行代码也快要完了(除非BT的再作个死循环)

NOTE: windows系统知道创建一个线程的唯一方法是调用APICreatThread函数(__beginthreadex之类的都要在内部调用他创建新线程)

好像windows核心编程说,在win2000下,系统用CreateRemoteThread函数来创建线程,CreateThread在内部调用CreateRemoteThread

不过这不是争论的焦点,至少win98CreateRemoteThread并不能正常工作,还是CreateThread主持大局。


3,在整个消息循环的机制中,还必须谈到窗口函数的可重入性。

什么意思?就是窗口函数(他是个回调函数)的代码什么时候都可以被系统(调用者一般是user32模块)调用。

比如在窗口过程中,向自己的窗口SendMessage(***);那么执行过程是怎样的?

我们知道,SendMessage是要等到消息发送并被目标窗口执行完之后才返回的

那么窗口在处理消息,然后又等待刚才发送到本窗口的消息被处理后之后(SendMessage返回)才继续往下执行,程序不就互相死锁了吗?其实是不会的。

windows设计一套适合SendMessage的算法,

    他判断如果发送的消息是属于本线程创建的窗口的,那么直接由user32模块调用窗口函数(可能就有窗口重入)

    并将消息的处理结果结果返回。这样做体现了窗口重入。上面的例子,我们调用SendMessage(***)发送消息到本窗口,

    那么窗口过程再次被调用,处理完消息之后将结果返回,然后SendMessage之后的程序接着执行。

    对于非队列消息,如果没有窗口重入,不知道会是什么样子。


NOTE: 由于窗口的可重入性。

win32SDK程序中应尽量少用全局变量和静态变量,因为在窗口函数执行过程中可能窗口重入,

如果重入后将这些变量改了,但你的程序在窗口重入返回之后继续执行,可能就是使用已经改变的全局或静态变量。

MFC(所有窗口的窗口函数基本上都是AfxWndProc),按照类的思想进行了组织,一般变量都是类中的,好管理的多。

4,MFC中窗口类(比如C**View,CFrameWnd)中的MessageBox函数,以及AfxMessageBox函数都是阻塞原有的消息循环的。

由消息框内部的一个消息循环来从消息队列中读取消息,并派送消息(和模式对话框类似)

实际上,这些消息函数最终调用的是::MessageBox,它在消息框内部实现了一个消息循环(原有的主程序消息循环被阻塞了)

论坛中碰到过几次关于计时器和消息框的问题,看下面的代码:

void CTest_recalclayoutView::OnTimer(UINTnIDEvent) {    // TODO: Add your message handler code here and/or calldefault    MessageBox("abc");    while(1); //设计一个死循环    CView::OnTimer(nIDEvent);}

咱让OnTimer大约5秒钟弹出一个消息框。那么,消息框不断的被弹出来,只要消息框不被关闭,那么程序就不会进入死循环。

实际上,每次弹出对话框,都是最上层的那个消息框掌握着消息循环,其他的消息循环被阻塞了。

只要不关闭最上面的消息框,while(1);就得不到执行。

如果点了关闭,程序就进入了死循环,只能用ctrl+alt+del来解决问题了。

5,消息循环在很多地方都有应用。

比如应用在线程池中。一个线程的执行周期一般在线程函数返回之后结束,那么怎么延长线程的生命周期呢?

一种方法就是按照消息循环的思想,在线程中加入消息循环,不断地从线程队列读取消息,并处理消息,线程的生命周期就保持着直到这个消息循环的退出。

NOTE:只要线程有界面元素或者调用GetMessage,或者有线程消息发送过来,系统就会为线程创建一个消息队列。


6,在单线程程序中,如果要执行一个长时间的复杂操作而且界面要有相应的话,可以考虑用自己的消息泵。

比如,可以将一个阻塞等待操作放在一个循环中,并将超时值设置得比较小,然后每个等待的片段中用消息泵继续消息循环,使界面能够响应用户操作。

等等之类,都可以应用消息泵(调用一个类似这样的函数)

BOOL CChildView::PeekAndPump(){    MSG msg;    while(::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))    {        if(!AfxGetApp()->PumpMessage())        {            ::PostQuitMessage(0);            return false;        }    }    return true;}


其实,用多线程也能解决复杂运算时的界面问题,但是没有这么方便,而且一般要加入线程通信和同步,考虑的事情更多一点。

综上所述,MFC消息循环就那么回事,主要思想还是和SDK中差不多。

这种思想主要的特点表现在迎合MFC整个框架上,为整个框架服务,为应用和功能服务。这是我的理解。呵呵~


///////////////////////////////////////////////////////////////////////////////////////////
MFC程序的执行顺序依次是:theApp全局对象定义处、TestApp构造函数、WinMain。
程序在加载main函数之前,会先为全局变量和全局对象分配内存空间。
对于MFC程序来说,通过产生一个应用程序类的对象来唯一标识应用程序的实例。每一个MFC程序实例有且仅有一个该派生类的实例化对象,也就是theApp全局对象,该对象就表示了应用程序本身。
theApp对象的构造函数CtestApp在调用之前,会调用其父类CWinApp的构造函数,从而就把我们程序自己创建的类与Microsoft提供的基类关联起来了。CWinApp的构造函数完成程序运行时的一些初始化工作。


AfxWinMain函数:WinMain函数实际上是通过调用AfxWinMain函数来完成它的功能的。
AfxWinMain调用AfxGetThread函数获得一个CWinTread类型的指针。
接着AfxWinMain调用AfxGetApp函数获得一个CWinApp类型的指针。
由以下代码可以看出:AfxGetThread函数返回的就是AfxGetApp函数的结果。
CWinThread* AFXAPI AfxGetThread()
{
        // check for current thread in module thread state
        AFX_MODULE_THREAD_STATE* pState = AfxGetModuleThreadState();
        CWinThread* pThread = pState->m_pCurrentWinThread;
        // if no CWinThread for the module, then use the global app
        if (pThread == NULL)
                pThread = AfxGetApp();
        return pThread;
}
因此,AfxWinMain函数中的pThread和pApp这两个指针是一致的。

AfxGetApp函数返回的是在CWinApp构造函数中保存的this指针(详见CWinApp类定义的源文件:appcore.cpp)。
对本Test程序来说,这个this指针实际上指向的是CTestApp的对象:theApp。也就是说,pThread和pApp所指向的都是CTestApp类的对象,即theApp全局对象。

InitInstance函数
pThread和pApp调用了三个函数(InitApplication、InitInstance和Run)去完成Win32程序所需要的几个步骤:设计窗口类、注册窗口类、创建窗口、显示窗口、更新窗口、消息循环以及窗口过程函数。
pApp首先调用InitApplication函数完成MFC内部管理方面的工作。
接着,调用pThread的InitInstance函数(其实是调用从CWinApp派生的应用程序类CTestApp中的虚函数InitInstance)。


MFC框架窗口
1.设计和注册窗口:MFC已经为我们预定义了一些默认的标准窗口类,只需呀选择所需的窗口类,然后注册就可以了。
窗口类的注册由AfxEndDeferRegisterClass函数完成(AfxEndDeferRegisterClass函数首先判断窗口类的类型,然后赋予其相应的类名,这些类名是MFC预定义的,然后调用AfxRegisterClass函数注册窗口类)。
AfxRegisterClass函数首先获得窗口类的信息,如果该窗口已经注册,则直接返回一个真值;如果尚未注册,就调用RegisterClass函数注册该窗口类(注册窗口类使用的函数其实和Win32 SDK编程中所使用的函数一致)。
我们所创建的这个MFC应用程序Test,实际上有两个窗口。
其中之一:CMainFrame::PreCreateWindow,这是在窗口产生之前被调用的。CMainFrame::PreCreateWindow函数又调用了AfxDeferRegisterClass,而AfxDeferRegisterClass实际上是一个宏,指向AfxEndDeferRegisterClass(前面提到,此函数的功能就是注册窗口类)。

2.创建窗口:
窗口的创建是由CWnd类的CreateEx函数实现的。(声明:AFXWin.h实现:WINCORE.CPP)
(在MFC的底层代码中CFrameWnd::Create调用了上述的CreateEx函数,而CFrameWnd::LoadFrame又调用CFrameWnd::Create函数。此过程请自行跟踪。)

CWnd::CreateEx调用CMainFrame::PreCreateWindow(PreCreateWindow是一个虚函数,所以这里实际上调用的是子类,即CMainFrame::PreCreateWindow。这里再次调用此函数是为了在产生窗口之前让程序员有机会修改程序的外观。例如,去掉窗口最大化按钮等)

3.显示和更新窗口

CTestApp::m_pMainWnd
m_pMainWnd是一个CWnd类型并且保存了应用程序框架窗口对象的指针,也就是说m_pMainWnd变量是一个指向CMainFrame对象的指针。
CTestApp::InitInstance函数实现内部有如下两行代码:
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
这两行代码实现了窗口的显示和更新。


消息循环

CWinThread::Run函数完成消息循环。

该函数由AfxWinMain::pThread函数调用。
形式:pThread->Run();

CWinThread::Run函数主要是一个for循环,该循环在收到一个WM_QUIT消息时退出。在此循环中调用了一个PumpMessage函数。


窗口过程函数

AfxEndDeferRegisterClass函数源程序:wndcls.lpfnWndProc = DefWindowProc
这里指定的一个默认窗口过程DefWindowProc。但实际上MFC程序并不是把所有的消息都交给DefWindowProc这一默认窗口过程来处理。而是采用了一种“消息映射机制”。


///////////////////////////////////////////////////////////////////////////////////////////
梳理全过程

1.首先利用全局应用程序对象theApp启动应用程序。正是产生了这个全局对象,基类CWinApp中的this指针才能指向这个对象。如果没有这个全局对象,程序在编译的时候不会出错,但在运行时就出错。
2.调用全局应用程序对象的构造函数,从而就会先调用其基类CWinApp的构造函数。后者完成应用程序的一些初始化工作,并将应用程序对象的指针保存起来。
3.进入WinMain函数。在AfxWinMain函数中可以获取子类(对Test程序来说,就是CTestApp类)的指针,利用此指针调用虚函数:InitInstance,根据多态性原理,实际上调用的是子类(CTestApp)的InitInstance函数。后者完成一些应用程序的初始化工作,包括窗口类的注册、创建,窗口的显示和更新。期间会多次调用CreateEx函数,因为一个单文档MFC应用程序有多个窗口,包括框架窗口、工具条、状态条等。
4.进入消息循环。虽然也设置了默认的窗口过程函数,但是,MFC应用程序实际上采用消息影射机制来处理各种消息的。当收到WM_QUIT消息时,退出消息循环,程序结束。
///////////////////////////////////////////////////////////////////////////////////////////






原创粉丝点击