线程

原创 2003年07月24日 08:30:00

进入MFC讲坛的前言(二)

2003-4-1 8:09:12   YESKY   毛守焱   阅读次数: 2420
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/n");

     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的控制中心――CWinThread::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()的核心――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的消息分发模式。

<三>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()处理,在某些情况下,要仔细处理,以免漏掉消息。

主线程和子线程的区别

**主线程和子线程的区别** 每个线程都有一个唯一标示符,来区分线程中的主次关系的说法。 线程唯一标示符:Thread.CurrentThread.ManagedThreadID; UI界面和...
  • qq_23833037
  • qq_23833037
  • 2016年07月07日 12:26
  • 9495

什么是线程?

什么是线程?       线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,...
  • boshuzhang
  • boshuzhang
  • 2016年03月19日 16:51
  • 6001

关于“内核线程”、“用户线程”概念的理解

今天偶然谈起了进程的相关概念,发现其中有许多不清晰的地方,现就以上的概念做一些研究,所参考的资料全部来自于网络,所以对于其中不正确的地方,欢迎大家给我指正,让我能够对以上概念更加清晰。...
  • u012927281
  • u012927281
  • 2016年06月09日 22:50
  • 6359

线程池技术启动多线程

每个变量的作用都已经标明出来了,这里要重点解释一下corePoolSize、maximumPoolSize、largestPoolSize三个变量。   corePoolSize在很多地方被翻译成核...
  • xu754736330
  • xu754736330
  • 2016年06月05日 12:31
  • 919

线程同步和线程安全

线程同步 同步就是协同步调,按预定的先后次序进行运行。如:你说完,我再说。 “同”字从字面上容易理解为一起动作 其实不是,“同”字应是指协同、协助、互相配合。 如进程、线程同步...
  • u012320991
  • u012320991
  • 2016年02月21日 15:18
  • 2334

线程的状态、分类及优先级

转转请注明出处:        上一篇博文我们简单地认识了关于多线程的概念以及使用Java语言实现多线程,这算是我们对Java并发编程学习的一个入门吧,那本篇博文我们将继续更深入地学习多线程...
  • yegongheng
  • yegongheng
  • 2014年08月20日 16:58
  • 3768

线程与服务的区别

经常会碰到的一个问题就是线程和服务有什么区别啊?线程与进程有什么区别啊: 1.线程与进程的区别: 进程是指运行中的应用程序,每一个进程都有自己独立的内存空间。一个应用程序可以同时启动多个进...
  • baopengjian
  • baopengjian
  • 2015年03月11日 20:09
  • 1811

线程基础:线程池(5)——基本使用(上)

从本文开始,我将用两篇文章的篇幅,为各位读者呈现JAVA中原生的线程池技术。第一篇文章,我将讲解JAVA原生线程池的基本使用,并由此延伸出JAVA中和线程管理相关的类结构体系,然后我们详细描述JAVA...
  • yinwenjie
  • yinwenjie
  • 2016年01月23日 10:02
  • 8778

对Java线程安全与不安全的理解

当我们查看JDK API的时候,总会发现一些类说明写着,线程安全或者线程不安全,比如说StringBuilder中,有这么一句,“将StringBuilder 的实例用于多个线程是不安全的。如果需要这...
  • fuzhongmin05
  • fuzhongmin05
  • 2017年03月01日 16:44
  • 2850

线程复用:线程池笔记

线程复用:线程池线程池《实战Java高并发程序设计》读书笔记。线程池,中有一定数量的活跃线程,供给系统调用,以减轻系统频繁创建/销毁线程的压力。...
  • u011244202
  • u011244202
  • 2017年04月04日 23:07
  • 570
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:线程
举报原因:
原因补充:

(最多只允许输入30个字)