MFC技术内幕系列

                     ///////////////////////////////////////////////////////////////////////////////////
                     /********* 文章系列:MFC技术内幕系列***********/
                     /************MFC技术内幕系列之(一)***********/
                     /****文章题目:MFC应用程序“生死因果”内幕*****/
                     /*                     Copyright(c)2002 bigwhite                  */
                     /*                          All rights Reserved                         */
                     /***********关键字:MFC,生死因果**************/
                     /*                           时间:2002.7.23                          */
                     /*     注释:本文所涉及的程序源代码均在Microsoft  */
                     /        Visual Studio.Net Enterprise Architect Edition       /
                     /*                    开发工具包提供的源代码中              */
                    
////////////////////////////////////////////////////////////////////////////////////////////////
引言:
     侯捷老师在他那本著名的"深入浅出MFC"(第二版)的第六章中对比着传统的Win32API编程,详细讲解了MFC应用程序“生死因果”,而且侯捷老师 还在"深入浅出MFC"(第二版)一书的“无责任书评”中称应用程序和MFC Framework的因果关系,是学习MFC程序设计的关键,并把它作为学习MFC程序设计的"第一个台阶".
作为已是“过来人”的我非常赞同侯捷老师的观点,特写下此篇文章以供大家参考,本文章特别对MFC程序设计的初学者大有裨益。

正文:
     初学MFC程序设计的人(甚至包括已经很精通Win32API编程的大虾们)都会感到很疑惑,对MFC应用程序的运行流程不能马上领悟,多数人都会提出类 似"WinMain函数跑到哪里去了?","窗口函数(WinProc),消息循环好像一下子都消失了?"等问题。下面就让我们看一个MFC SDI应用程序的运行流程并挖掘一下MFC库的源代码,来尽力争取弄清MFC应用程序“生死因果”的内幕。
                          ////////////////////////////////////////////
                          /*    1. Windows 帮忙         */
                          /*         程序诞生!         */
                          //////////////////////////////////////////
    Windows 操作系统为应用程序创建进程核心对象,并为该应用程序分配4GB的进程地址空间,系统加载器
将应用程序可执行文件映像以及一些必要的代码(包括数据和一些应用程序使用的dlls)加载到应用程序的进程地址空间中。
 
                          /////////////////////////////////////////////
                          /*    2.启动函数是什么?      */
                          /////////////////////////////////////////////
    Windows 操作系统在初始化该应用程序进程的同时,将自动为该应用程序创建一个主线程,该主线程与
C/C++运行时库的启动函数一道开始运行。很多初学者并不知道C/C++运行时库的启动函数是何方神圣,这里我
简单介绍一下:当你的应用程序编译后开始链接时,系统的链接器会根据你的应用程序的设置为你的应用程序
选择一个C/C++运行时库的启动函数(注释:这些函数声明在../Visual Studio.NET/vc7/crt/src/crt0.c中)
一般的ANSI版本的GUI的应用程序的C/C++运行时库的启动函数为:
                           int WinMainCRTStartup(void);
其它版本的C/C++运行时库的启动函数如下:
    ANSI版本的CUI的应用程序:      int mainCRTStartup(void);
    Unicode版本的CUI的应用程序:   int wmainCRTStartup(void);
    Unicode版本的GUI的应用程序:   int wWinMainCRTStartup(void);
    C/C++运行时库的启动函数的主要功能为初始化C/C++运行时库和为所有全局和静态的C++类对象调用构造函数。
                         /////////////////////////////////////////////////
                         /*   3.侯捷老师所说的"引爆器" */
                         /////////////////////////////////////////////////
    前面所说的C/C++运行时库的启动函数的主要功能之一是为所有全局和静态的C++类对象调用构造函数。侯捷老师所说的"引爆器"--- CMyWinApp theApp这个Application Object就是由启动函数调用其构造函数构造出来的。CWinApp的构造函数到底作了什么?看看源代码吧,源代码最能说明问题了。
    注释:CWinApp的构造函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp
 CWinApp::CWinApp(LPCTSTR lpszAppName)
{
 if (lpszAppName != NULL)
  m_pszAppName = _tcsdup(lpszAppName);
 else
  m_pszAppName = NULL;

 // initialize CWinThread state
 AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
 AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
 ASSERT(AfxGetThread() == NULL);
 pThreadState->m_pCurrentWinThread = this;
 ASSERT(AfxGetThread() == this);
 m_hThread = ::GetCurrentThread();
 m_nThreadID = ::GetCurrentThreadId();

 // initialize CWinApp state
 ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
 pModuleState->m_pCurrentWinApp = this;
 ASSERT(AfxGetApp() == this);

 // in non-running state until WinMain
 m_hInstance = NULL;
 m_hLangResourceDLL = NULL;
 m_pszHelpFilePath = NULL;
 m_pszProfileName = NULL;
 m_pszRegistryKey = NULL;
 m_pszExeName = NULL;
 m_pRecentFileList = NULL;
 m_pDocManager = NULL;
 m_atomApp = m_atomSystemTopic = NULL;
 m_lpCmdLine = NULL;
 m_pCmdInfo = NULL;

 // initialize wait cursor state
   ...//
 // initialize current printer state
   ...//
 // initialize DAO state
 m_lpfnDaoTerm = NULL;   // will be set if AfxDaoInit called

 // other initialization
   ...//
}
    从源代码中可以看出CWinApp的构造函数主要收集了一些关于应用程序主线程的信息及初始化一些相关应用程序的信息。值得注意的是CWinApp类的一 些主要的数据成员如:m_hInstance,m_lpCmdLine,m_pCmdInfo及m_atomApp等都初始化为NULL,这些成员在后面 将被重新赋值。
                        //////////////////////////////////////////////
                        /*    4. WinMain函数登场了    */
                        //////////////////////////////////////////////
    C/C++运行时库的启动函数int WinMainCRTStartup(void);所调用的WinMain函数---同时也是主线程的入口函数为:
    int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine
                         ,int nCmdShow);
    注释1:该函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appmodul.cpp中
    注释2:_t 是为了照顾Unicode版本而定义的宏。
    讲到这个时候你也许会稍稍展开你那紧皱的眉头,不过也许你还会问:"MFC中的WinMain函数到底作了什么?" 其实很简单,看看源代码就知道了。

    extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
 LPTSTR lpCmdLine, int nCmdShow)
{
 // call shared/exported WinMain
 return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
    这一下清楚了,MFC中的WinMain函数其实什么也没做,只是调用了一个函数AfxWinMain。

                       /////////////////////////////////////////////////
                       /*   5.MFC程序的入口点函数    */
                       //////////////////////////////////////////////////
   MFC作了一个"乾坤大挪移",将WinMain函数的全部责任转移交给了MFC程序的入口点函数---AfxWinMain。
   注释:该函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/winmain.cpp中。

// Standard WinMain implementation
//  Can be replaced as long as 'AfxWinInit' is called first

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
 LPTSTR lpCmdLine, int nCmdShow)
{
 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-NULL m_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;
}


   从上面源代码可以看出AfxWinMain函数主要由四大模块组成,他们分别是AfxWinInit,InitApplication,
InitInstance,Run。下面将分别介绍这四大模块的功能。

                       ///////////////////////////////////////////////
                       /*   5.1  AFX的内部初始化     */
                       ///////////////////////////////////////////////
   AfxWinInit函数是既CWinApp类构造函数后的又一个重量级的函数。不妨看一下它的源代码:
   注释:该函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appinit.cpp中。
   BOOL AFXAPI AfxWinInit(HINSTANCE hInstance, HINSTANCE hPrevInstance,
     LPTSTR lpCmdLine, int nCmdShow)
    {
 ASSERT(hPrevInstance == NULL);


 // handle critical errors and avoid Windows message boxes
 SetErrorMode(SetErrorMode(0) |
  SEM_FAILCRITICALERRORS|SEM_NOOPENFILEERRORBOX);

 // set resource handles
 AFX_MODULE_STATE* pModuleState = AfxGetModuleState();
 pModuleState->m_hCurrentInstanceHandle = hInstance;
 pModuleState->m_hCurrentResourceHandle = hInstance;

 // fill in the initial state for the application
 CWinApp* pApp = AfxGetApp();
 if (pApp != NULL)
 {
  // Windows specific initialization (not done if no CWinApp)
  pApp->m_hInstance = hInstance;
  hPrevInstance; // Obsolete.
  pApp->m_lpCmdLine = lpCmdLine;
  pApp->m_nCmdShow = nCmdShow;
  pApp->SetCurrentHandles();
 }

 // initialize thread specific data (for main thread)
 if (!afxContextIsDLL)
  AfxInitThread();

 // Initialize CWnd::m_pfnNotifyWinEvent
 HMODULE hModule = ::GetModuleHandle(_T("user32.dll"));
 if (hModule != NULL)
 {
  CWnd::m_pfnNotifyWinEvent = (CWnd::PFNNOTIFYWINEVENT)::GetProcAddress(hModule,                                               "NotifyWinEvent");
 }

 return TRUE;
   }
   还记得我在第三个标题---侯捷老师所说的"引爆器"处 的话么,"CWinApp类的一些主要的数据成员在后面将被重新赋值。",AfxWinInit函数就是这些数据成员被赋值的地方,它重新初始化这些在整 个程中都扮演重要角色的成员,并且调用AfxInitThread()为主线程作了一些初始化工作,这些都为以后MFC框架的正常运作铺平了道路。

                       /////////////////////////////////////////////////
                       /* 5.2  应用程序的全局初始化  */
                       /////////////////////////////////////////////////
  InitApplication函数(virtual)为程序进行全局初始化:
  注释1:该函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp中。
  BOOL CWinApp::InitApplication()
 {
 if (CDocManager::pStaticDocManager != NULL)
 {
  if (m_pDocManager == NULL)
   m_pDocManager = CDocManager::pStaticDocManager;
  CDocManager::pStaticDocManager = NULL;
 }

 if (m_pDocManager != NULL)
  m_pDocManager->AddDocTemplate(NULL);
 else
  CDocManager::bStaticInit = FALSE;

 LoadSysPolicies();
        return TRUE;
 }
 由于初次调用时 CDocManager::pStaticDocManager==0x00000000;m_pDocManager==0x00000000;所以 InitApplication函数只是调用了CWinApp::LoadSysPolicies();而后者将加载一些注册表的信息用来初始化一些程序 定义的结构并为程序注册一些基本信息。(由于该函数可能尚未文档化,所以关于LoadSysPolicies函数的说明只是看了源代码后的推测,下面列出 了它的部分源代码仅供参考)
   注释2:该函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp中。
    BOOL CWinApp::LoadSysPolicies()
   {
 HKEY hkPolicy = NULL;
 DWORD dwValue = 0;
 DWORD dwDataLen = sizeof(dwValue);
 DWORD dwType = 0;

 // clear current policy settings.
 m_dwPolicies = _AFX_SYSPOLICY_NOTINITIALIZED;

 static _AfxSysPolicyData rgExplorerData[] =
 {
  {_T("NoRun"), _AFX_SYSPOLICY_NORUN},
  {_T("NoDrives"), _AFX_SYSPOLICY_NODRIVES},
  {_T("RestrictRun"), _AFX_SYSPOLICY_RESTRICTRUN},
  {_T("NoNetConnectDisconnect"), _AFX_SYSPOLICY_NONETCONNECTDISCONNECTD},
  {_T("NoRecentDocsHistory"), _AFX_SYSPOLICY_NORECENTDOCHISTORY},
  {_T("NoClose"), _AFX_SYSPOLICY_NOCLOSE},
  {NULL, NULL}
 };

 ...//

 static _AfxSysPolicyData rgComDlgData[] =
 {
  {_T("NoPlacesBar"), _AFX_SYSPOLICY_NOPLACESBAR},
  {_T("NoBackButton"), _AFX_SYSPOLICY_NOBACKBUTTON},
  {_T("NoFileMru"), _AFX_SYSPOLICY_NOFILEMRU},
  {NULL, NULL}
 };

 static _AfxSysPolicies rgPolicies[] =
 {
  {_T("Software//Microsoft//Windows//CurrentVersion//Policies//Explorer"),
   rgExplorerData},
  {_T("Software//Microsoft//Windows//CurrentVersion//Policies//Network"),
   rgNetworkData},
  {_T("Software//Microsoft//Windows//CurrentVersion//Policies//Comdlg32"),
   rgComDlgData},
  {NULL, NULL}
 };

 _AfxSysPolicies *pPolicies = rgPolicies;
 _AfxSysPolicyData *pData = NULL;
        ...//
  }
   注释3:在MFC文档中有这么一句话"The CWinApp::InitApplication member function is obsolete in MFC.",所以你大多情况下不用在意这个virtual函数。


                       /////////////////////////////////////////////////
                       /*  5.3 应用程序的标准实例化  */
                       //////////////////////////////////////////////////
   CWinApp::InitInstance()是一个虚函数,大多数应用程序都要override这个函数。让我们看看应用程序向导MFC AppWizard(.exe)为SDI 程序作出的override后的代码吧!
   BOOL CMyWinApp::InitInstance()
  {
 // 如果一个运行在 Windows XP 上的应用程序清单指定要
 // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
 //则需要 InitCommonControls()。否则,将无法创建窗口。
 InitCommonControls();

 CWinApp::InitInstance();//显式调用基类的InitInstance()

 // 初始化 OLE 库
 if (!AfxOleInit())
 {
  AfxMessageBox(IDP_OLE_INIT_FAILED);
  return FALSE;
 }
 
 AfxEnableControlContainer();
 // 标准初始化
 
 SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
 LoadStdProfileSettings(4);  // 加载标准 INI 文件选项(包括 MRU)
 // 注册应用程序的文档模板。
 // 文档模板将用作文档、框架窗口和视图之间的连接
 CSingleDocTemplate* pDocTemplate;
 pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CTestDoc),
  RUNTIME_CLASS(CMainFrame),       // 主 SDI 框架窗口
  RUNTIME_CLASS(CTestView));
 pDocTemplate->SetContainerInfo(IDR_CNTR_INPLACE);
 AddDocTemplate(pDocTemplate);
 // 分析标准外壳命令、DDE、打开文件操作的命令行
 CCommandLineInfo cmdInfo;
 ParseCommandLine(cmdInfo);
 
 if (!ProcessShellCommand(cmdInfo))
  return FALSE;
 // 唯一的一个窗口已初始化,因此显示它并对其进行更新
 m_pMainWnd->ShowWindow(SW_HIDE);
 m_pMainWnd->UpdateWindow();
 
 return TRUE;
}

  CMyWinApp::InitInstance()先显式调用了基类的InitInstance();
  我们先看看这个基类的函数的定义吧!
  注释1:该函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp中。
 
   BOOL CWinApp::InitInstance()
  {
 InitLibId();
 m_hLangResourceDLL = LoadAppLangResourceDLL();
 if(m_hLangResourceDLL != NULL)
 {
  AfxSetResourceHandle(m_hLangResourceDLL);
  _AtlBaseModule.SetResourceInstance(m_hLangResourceDLL);
 }
        return TRUE;
  }
  注释2:vc.net中的CWinApp::InitInstance()已与vc6.0中的CWinApp::InitInstance()有所区别。
  基类的InitInstance()
    先 调用InitLibId()函数用于Initializes the data member containing the GUID of the current module;不过该函数现在为空,估计以后微软会填充该函数。
    之后调用LoadAppLangResourceDLL()函数加载应用程序所需资源;在vc6.0中的CWinApp::InitInstance()函数只有一条语句:即return TRUE;
    CMyWinApp::InitInstance()在其基类的帮助后,开始执行它自己的一系列代码来完成诸如"初始化 OLE 库","设置注册表主键以使程序能保存信息到注册表中","分析标准外壳命令","生成程序主框架,文档和视图结构","显示程序主窗口"等工作。
   注释3:有关应用程序是如何在CMyWinApp::InitInstance()完成上面一系列工作的,将在本系列文章之二            的“MFC文档视图结构内幕”一文中详述。
   注释4:在MSDN中有关InitInstance的叙述如下:"Windows allows several copies of the same program           to run at the same time."
                       ////////////////////////////////////////////
                       /*     5.4 "消息泵"启动了     */
                       ////////////////////////////////////////////
    众所周知,Windows是一个以消息为基础,以事件驱动的操作系统,每一个Win32程序也都是如此。那么
MFC应用程序是如何实现消息机制的呢?MFC应用程序框架将这种消息机制包装到了一个"消息泵"中,而这个"消息泵"在CMyWinApp::InitInstance()中被启动了。其源代码如下:
   注释1:该函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp中。
 // Main running routine until application exits
  int CWinApp::Run()
 {
 if (m_pMainWnd == NULL && AfxOleGetUserCtrl())
 {
  // Not launched /Embedding or /Automation, but has no main window!
  TRACE(traceAppMsg, 0, "Warning: m_pMainWnd is NULL in CWinApp::Run - quitting                        application./n");
  AfxPostQuitMessage(0);
 }
 return CWinThread::Run();
 }
 由上面的源代码看出:CWinApp::Run()调用了其基类的Run()函数,继续看源代码:
  int CWinThread::Run()
 {
 ASSERT_VALID(this);
 _AFX_THREAD_STATE* pState = AfxGetThreadState();

 // 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(&(pState->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))
   if (IsIdleMessage(&(pState->m_msgCur)))
   {
    bIdle = TRUE;
    lIdleCount = 0;
   }

  } while (::PeekMessage(&(pState->m_msgCur), NULL, NULL, NULL, PM_NOREMOVE));
 }
  }                   

 //CWinThread implementation helpers
  BOOL CWinThread::PumpMessage()
 {
  return AfxInternalPumpMessage();
 }

 BOOL AFXAPI AfxInternalPumpMessage()//部分源码
 {
 _AFX_THREAD_STATE *pState = AfxGetThreadState();
        if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
 {
             ...//
             return FALSE;
 }

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