⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 新建 文本文档.txt

📁 CRC校验 CSerialPort串口类C RC校验 CSerialPort串口类
💻 TXT
📖 第 1 页 / 共 2 页
字号:
 ASSERT(!(m_nFlags & WF_MODALLOOP)); // window must not already be in modal state

 // for tracking the idle time state
 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 messages until 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 the dialog when the message queue goes idle
   if (bShowIdle)
   {
    ShowWindow(SW_SHOWNORMAL);
    UpdateWindow();
    bShowIdle = FALSE;
   }

   // call OnIdle 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 while available
  do
  {
   ASSERT(ContinueModal());

   // pump message, but quit on WM_QUIT
   //PumpMessage(消息泵)的实现和上面讲的差不多。都是派送消息到窗口。
   if (!AfxGetThread()->PumpMessage())
   {
    AfxPostQuitMessage(0);
    return -1;
   }

   // show the window 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 "no idle" 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.cpp
void CWnd::EndModalLoop(int nResult)
{
 ASSERT(::IsWindow(m_hWnd));

 // this result will be returned from CWnd::RunModalLoop
 m_nModalResult = nResult;

 // make sure a message goes through to 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使用非模式对话框来模拟模式对话框,而在win32 SDK程序中,模式对话框激发他的父窗口Enable操作是没有效果的。

关于消息循环总结:


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

2,因为是单线程的(程序进程启动的时候,只有而且有一个线程,我们称他为主线程),所以就像邮递员一样,每次只能在某一个地方干活。什么意思呢?举个例子,用::DiapatchMessage派送消息,在窗口处理过程(WinProc,窗口函数)返回之前,他是阻塞的,不会立即返回,也就是消息循环此时不能再从消息队列中读取消息,直到::DispatchMessage返回。如果你在窗口函数中执行一个死循环操作,就算你用PostQuitMessage函数退出,程序也会down掉。
while(1)
{
    PostQuitMessage(0); //程序照样down.
}
所以,当窗口函数处理没有返回的时候,消息循环是不会从消息队列中读取消息的。这也是为什么在模式对话框中要自己用无限循环来继续消息循环,因为这个无限循环阻塞了原来的消息循环,所以,在这个无限循环中要用GetMessage,PeekMessage,DispatchMessage来从消息队列中读取消息并派送消息了。要不然程序就不会响应了,这不是我们所希望的。
所以说,消息循环放在程序的什么的地方都基本上是过的去的,比如放在DLL里面。但是,最好在任何时候,只有一个消息循环在工作(其他的都被阻塞了)。然后,我们要作好的一件事情,就是怎么从消息循环中退出!当然用WM_QUIT是可以拉~(PostThreadMessage也是个好主意),这个消息循环退出后,可能程序退出,也可能会激活另外一个被阻塞的消息循环,程序继续运行。这要看你怎么想,怎么去做。最后一个消息循环结束的时候,也许就是程序快结束的时候,因为主线程的执行代码也快要完了(除非BT的再作个死循环)。

NOTE: 让windows系统知道创建一个线程的唯一方法是调用API CreatThread函数(__beginthreadex之类的都要在内部调用他创建新线程)。好像windows核心编程说,在win2000下,系统用CreateRemoteThread函数来创建线程,CreateThread在内部调用CreateRemoteThread。不过这不是争论的焦点,至少win98下CreateRemoteThread并不能正常工作,还是CreateThread主持大局。

3,在整个消息循环的机制中,还必须谈到窗口函数的可重入性。什么意思?就是窗口函数(他是个回调函数)的代码什么时候都可以被系统(调用者一般是user32模块)调用。比如在窗口过程中,向自己的窗口SendMessage(***);那么执行过程是怎样的?
我们知道,SendMessage是要等到消息发送并被目标窗口执行完之后才返回的。那么窗口在处理消息,然后又等待刚才发送到本窗口的消息被处理后之后(SendMessage返回)才继续往下执行,程序不就互相死锁了吗? 
其实是不会的。windows设计一套适合SendMessage的算法,他判断如果发送的消息是属于本线程创建的窗口的,那么直接由user32模块调用窗口函数(可能就有窗口重入),并将消息的处理结果结果返回。这样做体现了窗口重入。上面的例子,我们调用SendMessage(***)发送消息到本窗口,那么窗口过程再次被调用,处理完消息之后将结果返回,然后SendMessage之后的程序接着执行。对于非队列消息,如果没有窗口重入,不知道会是什么样子。

NOTE: 由于窗口的可重入性。在win32 SDK程序中应尽量少用全局变量和静态变量,因为在窗口函数执行过程中可能窗口重入,如果重入后将这些变量改了,但你的程序在窗口重入返回之后继续执行,可能就是使用已经改变的全局或静态变量。在MFC中(所有窗口的窗口函数基本上都是AfxWndProc),按照类的思想进行了组织,一般变量都是类中的,好管理的多。

4,MFC中窗口类(比如C**View,CFrameWnd等)中的MessageBox函数,以及AfxMessageBox函数都是阻塞原有的消息循环的。由消息框内部的一个消息循环来从消息队列中读取消息,并派送消息(和模式对话框类似)。实际上,这些消息函数最终调用的是::MessageBox,它在消息框内部实现了一个消息循环(原有的主程序消息循环被阻塞了)。论坛中碰到过几次关于计时器和消息框的问题,看下面的代码:
void CTest_recalclayoutView::OnTimer(UINT nIDEvent) 
{
 // TODO: Add your message handler code here and/or call default
 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整个框架上,为整个框架服务,为应用和功能服务。这是我的理解。呵呵~

 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -