MFC再学习(一)单文档程序刨根究底

温故而知新。在.NET等等新技术前面,MFC虽然有些老旧,甚至是显得晦涩和有些奇技淫巧,但是其中的很多思想还是值得我们学习的。侯捷老师的《深入浅出MFC》基本是学习MFC的必读教材,但是因为年代久远,MFC内部也有不少东西发生了变化,而后又有《MFC技术内幕系列》,但又是基于MDI的。于是我觉得有必要从头再梳理一遍,算是做个备忘,也是个再学习的过程。让我们从单文档程序的生死流程开始。请注意本文会对许多细节进行更深的挖掘,因此会有很多源码,也许读起来会有些累。

 

首先用向导生成一个单文档程序,项目名称为SDI(本文均基于MFC10.0)。

1.进入WinMain前的准备工作

shell调用win32 API函数CreateProcess激活我们的应用程序实例,也就是建立一个「进程核心对象」,并为此进程建立一个4GB的 地址空间。加载器将必要的码加载到上述地址空间中,包括App.exe 的程序、资料,以及所需的动态联结函数库(DLLs)。系统为此进程建立一个执行线程,称为主执行线程(primary thread),同时调用C Runtime Library中的启动函数,

该函数共有4个版本,对应如下:

MFC再学习(一)单文档程序刨根究底_第1张图片

它们都定义在VS安装目录/VC/crt/src/crt0.c中,这里将被调用的是WinMainCRTStartup。

而实际上他们所作的工作是一样的,crt0.c中有如下这一段:

#ifdef _WINMAIN_

#ifdef WPRFLAG

#define _tmainCRTStartup   wWinMainCRTStartup

#else  /*WPRFLAG */

#define _tmainCRTStartup   WinMainCRTStartup

#endif  /*WPRFLAG */

#else  /*_WINMAIN_ */

#ifdef WPRFLAG

#define _tmainCRTStartup    wmainCRTStartup

#else  /*WPRFLAG */

#define _tmainCRTStartup    mainCRTStartup

#endif  /*WPRFLAG */

#endif  /*_WINMAIN_ */

实际上会调用哪个启动函数就是由_WINMAIN_和WPRFLAG这两个宏来定义的,而他们实质上都是_tmainCRTStartup,而_tmainCRTStartup又调用__tmainCRTStartup,其原型如:

__declspec(noinline)int __tmainCRTStartup(void )

 

__declspec(noinline)是告诉编译器不要将其作为内联函数,在这个函数中主要做了1.调用_heap_init函数创建堆空间,调用_mtinit启用多线程 2.RTC,低级IO初始化 3.获取命令行缓冲区、环境变量的指针,并设置命令行参数与环境变量4. 调用_cinit初始化C和C++数据,包括初始化浮点计算包,调用_initterm( __xi_a,__xi_z)执行注册,PE文件数据区的初始化函数,调用_initterm(__xc_a, __xc_z)执行C++的初始化,这其中就会通过一个指针来遍历成员函数指针表,分别调用各个全局对象的构造函数,代表着我们程序的CSDIApp的唯一实例化对象theApp也在这时被构造(最终调用的是基类CWinApp的构造函数),这时theApp中的成员变量都是空值,将在之后被重新赋值。

_initterm函数的代码如下:

#ifdef CRTDLL

void __cdecl _initterm (

#else  /* CRTDLL */

static void __cdecl _initterm (

#endif  /* CRTDLL */

        _PVFV* pfbegin,

        _PVFV* pfend

        )

{

        /*

         *walk the table of function pointers from the bottom up, until

         *the end is encountered.  Do not skip the first entry.  The initial

         *value of pfbegin points to the first valid entry.  Do not try to

         *execute what pfend points to.  Only entries before pfend are valid.

         */

        while( pfbegin < pfend )

        {

            /*

             *if current table entry is non-NULL, call thru it.

             */

            if( *pfbegin != NULL )//遍历

                (**pfbegin)();//全局对象构造函数调用

            ++pfbegin;

        }

}

然后根据_WINMAIN_宏是否被定义来调用了_tWinMain或者_tmain,根据UNICODE宏是否被定义来调用相应的版本。

crt0.c中有如下这一段:

#ifdef _WINMAIN_

          lpszCommandLine = _twincmdln();

          mainret = _tWinMain( (HINSTANCE)&__ImageBase,

                                 NULL,

                                lpszCommandLine,

                                 StartupInfo.dwFlags &STARTF_USESHOWWINDOW

                                      ?StartupInfo.wShowWindow

                                      :SW_SHOWDEFAULT

                                );

#else /* _WINMAIN_ */

           _tinitenv = _tenviron;

           mainret = _tmain(__argc, _targv, _tenviron);

 

tchar.h中有如下这一段:

#ifdef_UNICODE

#define_tmain      wmain

#define_tWinMain   wWinMain

#else   /* ndef _UNICODE */

#define_tmain      main

#define_tWinMain   WinMain

#endif

 

2.进入WinMain函数

全局对象TheApp被构造出来之后,就进入了WinMain函数。WinMain函数并非由用户编写,而是由MFC事先提供的,在链接时由链接器加入用户代码中。WinMain函数只是调用了AfxWinMain,这才是MFC程序真正的入口。

代码如下:

extern "C" int WINAPI _tWinMain(HINSTANCEhInstance, HINSTANCE hPrevInstance,

LPTSTR lpCmdLine, int nCmdShow)

{

// call shared/exported WinMain

return AfxWinMain(hInstance, hPrevInstance, lpCmdLine,nCmdShow);

}

 

2.1 AfxWinMain的调用

代码如下:

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

    _In_ LPTSTR lpCmdLine, intnCmdShow)

{

    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 AfxLockTempMapcalls

    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函数中,首先获取了主线程以及程序实例的指针,通过这两个指针来完成4大部分的工作,分别是AfxWinInit,InitApplication, InitInstance和Run。注意,这一部分已经与《深入浅出MFC》时的MFC4.x有了一定区别

 

2.1.1 AfxWinInit的调用

代码如下:

BOOL AFXAPI AfxWinInit(_In_HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,

    _In_z_ LPTSTR lpCmdLine, _In_ intnCmdShow)

{

    ASSERT(hPrevInstance == NULL);

    // handle critical errors and avoidWindows 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;

    pModuleState->CreateActivationContext();

 

    // fill in the initial state for theapplication

    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 (formain thread)

    if (!afxContextIsDLL)

        AfxInitThread();

    return TRUE;

}

可以看出AfxWinMain函数主要是做了一些初始化工作。将theApp的几个主要成员变量,赋值成通过参数形式传递进来的WinMain函数的4个参数。随后调用AfxInitThread()为主线程进行初始化。(源代码《深入浅出MFC》 P371提供了,这里不再赘述)

 

2.1.2 InitApplication的调用

InitApplication是个虚函数,一般不需要重载,因此通过pApp调用的是基类的CWinApp::InitApplication();

源代码如下:

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和m_pDocManager都为NULL,所以做的操作就是将CDocManager::bStaticInit 赋值为FALSE,这是个static的bool变量,当文档模板是静态初始化的时候应当置为true,否则应为false,我们这里是动态在堆上构造的。LoadSysPolicies()具体做了什么不太清楚,应该也无关紧要。

 

2.1.3 InitInstance的调用

InitApplication运行结束之后,将通过pThread调用InitInstance,这也是单文档程序中最重要的函数,窗口的注册,创建和显示都在这里完成。这也是个虚函数,我们应当在CSDIApp中重载这个函数。

函数定义如下:

BOOL CSDIApp::InitInstance()

{

    // 如果一个运行在 Windows XP 上的应用程序清单指定要

    // 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,

    //则需要 InitCommonControlsEx()。否则,将无法创建窗口。

    INITCOMMONCONTROLSEX InitCtrls;

    InitCtrls.dwSize = sizeof(InitCtrls);

    // 将它设置为包括所有要在应用程序中使用的

    // 公共控件类。

    InitCtrls.dwICC = ICC_WIN95_CLASSES;

    InitCommonControlsEx(&InitCtrls);

    CWinAppEx::InitInstance();

    // 初始化 OLE 库

    if (!AfxOleInit())

    {

        AfxMessageBox(IDP_OLE_INIT_FAILED);

        return FALSE;

    }

    AfxEnableControlContainer();

    EnableTaskbarInteraction(FALSE);

    // 使用 RichEdit 控件需要  AfxInitRichEdit2()  

    // AfxInitRichEdit2();

    // 标准初始化

    // 如果未使用这些功能并希望减小

    // 最终可执行文件的大小,则应移除下列

    // 不需要的特定初始化例程

    // 更改用于存储设置的注册表项

    // TODO: 应适当修改该字符串,

    // 例如修改为公司或组织名

    SetRegistryKey(_T("boli's app"));//这里是准备在注册表HKEY_CURRENT_USER\\software 下面生成一个boli's app分支~为什么说是准备呢?因为如果不调用相关函数,如上面提到的6个函数,它是不会真正读写注册表的。

    LoadStdProfileSettings(4); // 加载标准 INI 文件选项(包括 MRU)

    InitContextMenuManager();

    InitKeyboardManager();

    InitTooltipManager();

    CMFCToolTipInfo ttParams;

    ttParams.m_bVislManagerTheme = TRUE;

    theApp.GetTooltipManager()->SetTooltipParams(AFX_TOOLTIP_TYPE_ALL,

        RUNTIME_CLASS(CMFCToolTipCtrl), &ttParams);

    // 注册应用程序的文档模板。文档模板

    // 将用作文档、框架窗口和视图之间的连接

    CSingleDocTemplate* pDocTemplate;

    pDocTemplate = new CSingleDocTemplate(

        IDR_MAINFRAME,

        RUNTIME_CLASS(CMfcDrawDoc),

        RUNTIME_CLASS(CMainFrame),       //主SDI框架窗口

        RUNTIME_CLASS(CMfcDrawView));

    if (!pDocTemplate)

        return FALSE;

    AddDocTemplate(pDocTemplate);

    // 分析标准 shell 命令、DDE、打开文件操作的命令行

    CCommandLineInfo cmdInfo;

    ParseCommandLine(cmdInfo);

    // 调度在命令行中指定的命令。如果

    // 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。

    if (!ProcessShellCommand(cmdInfo))

        return FALSE;

    // 唯一的一个窗口已初始化,因此显示它并对其进行更新

    m_pMainWnd->ShowWindow(SW_SHOW);

    m_pMainWnd->UpdateWindow();

    // 仅当具有后缀时才调用 DragAcceptFiles

    //  在 SDI 应用程序中,这应在 ProcessShellCommand 之后发生

    return TRUE;

}

在一系列的初始化工作之后,我们new了一个CSingleDocTemplate对象,也就是申请了一块堆空间,这时将调用CSingleDocTemplate类的构造函数构造这个对象,并使pDocTemplate成为指向这块空间的指针。下面看一下CSingleDocTemplate类的构造函数做了些什么:
CSingleDocTemplate::CSingleDocTemplate(UINT nIDResource,

    CRuntimeClass* pDocClass, CRuntimeClass* pFrameClass,

    CRuntimeClass* pViewClass)

        :CDocTemplate(nIDResource, pDocClass, pFrameClass, pViewClass)

{

    m_pOnlyDoc = NULL;

}

原来是先调用了基类CDocTemplate的构造函数,将4个参数原封不动的传给了基类的构造函数,nIDResource用来表示此Document显现时应该采用的UI 对象(资源),这里我们传进来的是IDR_FRAMEWND,也就是我们的默认菜单资源的ID。而后三个就是文档,框架,视图类的CRuntimeClass指针,用于稍后的动态创建。而后将成员变量CDocument*m_pOnlyDoc初始化为NULL。

接下来看看CDocTemplate的构造函数做了什么:
CDocTemplate::CDocTemplate(UINT nIDResource, CRuntimeClass* pDocClass,

    CRuntimeClass* pFrameClass, CRuntimeClass* pViewClass)

{

    ASSERT_VALID_IDR(nIDResource);

    ASSERT(pDocClass == NULL ||

        pDocClass->IsDerivedFrom(RUNTIME_CLASS(CDocument)));

    ASSERT(pFrameClass == NULL ||

        pFrameClass->IsDerivedFrom(RUNTIME_CLASS(CFrameWnd)));

    ASSERT(pViewClass == NULL ||

        pViewClass->IsDerivedFrom(RUNTIME_CLASS(CView)));

 

    m_nIDResource = nIDResource;

    m_nIDServerResource = NULL;

    m_nIDEmbeddingResource = NULL;

    m_nIDContainerResource = NULL;

    m_nIDPreviewResource = NULL;

    m_pDocClass = pDocClass;

    m_pFrameClass = pFrameClass;

    m_pViewClass = pViewClass;

    m_pOleFrameClass = NULL;

    m_pOleViewClass = NULL;

    m_pPreviewFrameClass = NULL;

    m_pPreviewViewClass = NULL;

    m_pAttachedFactory = NULL;

    m_hMenuInPlace = NULL;

    m_hAccelInPlace = NULL;

    m_hMenuEmbedding = NULL;

    m_hAccelEmbedding = NULL;

    m_hMenuInPlaceServer = NULL;

    m_hAccelInPlaceServer = NULL;

    // add to pStaticList if constructed asstatic instead of on heap

    if (CDocManager::bStaticInit)

    {

        m_bAutoDelete = FALSE;

        if (CDocManager::pStaticList== NULL)

            CDocManager::pStaticList = newCPtrList;

        if(CDocManager::pStaticDocManager == NULL)

            CDocManager::pStaticDocManager = newCDocManager;

        CDocManager::pStaticList->AddTail(this);

    }

    else

    {

        m_bAutoDelete = TRUE;  // usually allocated on the heap

        LoadTemplate();

    }

}

在一些防御性代码之后,将4个参数赋值给4个对应的成员变量,并且判断了模板是静态构造的还是在堆上动态生成的,这里CDocManager::bStaticInit为FALSE,所以执行LoadTemplate(),它的作用是为CDocTemplate或者其衍生类加载资源。

之后执行AddDocTemplate(pDocTemplate),将刚刚创建的文档模板指针pDocTemplate加入到CDocManager所管理的文档模板链表m_templateList中。因为我们并没有在CSDIApp中改写AddDocTemplate这个函数,所以执行的是基类的CWinApp::AddDocTemplate()。让我们看下它的定义:

void CWinApp::AddDocTemplate(CDocTemplate* pTemplate)

{

    if (m_pDocManager == NULL)

        m_pDocManager = newCDocManager;

    m_pDocManager->AddDocTemplate(pTemplate);

}

运行到这里,终于将CDocManager的对象构造出来。很明显,这个CDocManager对象的指针赋给了CWinApp的成员变量CDocManager* m_pDocManager,那么CWinApp及其子类对象就能通过这个指针来访问CDocManager类,进而获取文档模板。因此,CDocManager类是连接CWinApp及其子类与文档模板的桥梁。侯捷老师的这幅图很好的说明了这一关系。

MFC再学习(一)单文档程序刨根究底_第2张图片

 

作为补充,将CDocManager类的定义放在下面(appui2.cpp):

classCDocManager : public CObject
{
 DECLARE_DYNAMIC(CDocManager)
 public:

  // Constructor
  CDocManager();

  //Document functions
  virtual void AddDocTemplate(CDocTemplate* pTemplate);
  virtual POSITION GetFirstDocTemplatePosition() const;
  virtual CDocTemplate*GetNextDocTemplate(POSITION& pos) const;
  virtual void RegisterShellFileTypes(BOOL bCompat);
  void UnregisterShellFileTypes();
  virtual CDocument* OpenDocumentFile(LPCTSTRlpszFileName); // open named file
  virtual BOOL SaveAllModified(); // save before exit
  virtual void CloseAllDocuments(BOOL bEndSession); //close documents before exiting
  virtual int GetOpenDocumentCount();

  // helper for standard commdlg dialogs
  virtual BOOL DoPromptFileName(CString& fileName,UINT nIDSTitle,
  DWORD lFlags, BOOL bOpenFileDialog, CDocTemplate*pTemplate);

  //Commands
  // Advanced: process async DDE request
  virtual BOOL OnDDECommand(LPTSTR lpszCommand);
  virtual void OnFileNew();
  virtual void OnFileOpen();

  // Implementation
 protected:
  CPtrList m_templateList;
  int GetDocumentCount(); // helper to count number oftotal documents

 public:
  static CPtrList* pStaticList; // for staticCDocTemplate objects
  static BOOL bStaticInit; // TRUE during staticinitialization
  static CDocManager* pStaticDocManager; // for staticCDocTemplate objects

 public:
  virtual ~CDocManager();
  #ifdef _DEBUG
   virtual void AssertValid() const;
   virtual void Dump(CDumpContext& dc) const;
  #endif
};

可以看到文档模板链表m_templateList是CPtrList类型的成员变量,而看了CPtrList的定义便知,它就是一个实现链表结构的类。另外还有一些对链表进行操作的成员函数。

 

在CDocManager对象被构造出来之后,CWinApp:: AddDocTemplate调用了CDocManager::AddDocTemplate。其源代码如下:

void CDocManager::AddDocTemplate(CDocTemplate*pTemplate)

{

 if (pTemplate == NULL)

 {

  if (pStaticList != NULL)

  {

   POSITION pos =pStaticList->GetHeadPosition();

   while (pos != NULL)

   {

    CDocTemplate* pTemplate =(CDocTemplate*)pStaticList->GetNext(pos);

    AddDocTemplate(pTemplate);

   }

   delete pStaticList;

   pStaticList = NULL;

  }

  bStaticInit = FALSE;

 }

 else

 {

  ASSERT_VALID(pTemplate);

  ASSERT(m_templateList.Find(pTemplate,NULL) == NULL);// must not be in list

  pTemplate->LoadTemplate();

  m_templateList.AddTail(pTemplate);

 }

}

将刚刚创建的文档模板的指针pDocTemplate作为参数传进来,因为pDocTemplate不为空,那么执行的就是m_templateList.AddTail(pTemplate),也就是将pDocTemplate加到m_templateList链表的末尾。至此,刚刚创建的文档模板就加入了文档模板链表m_templateList中,以供CDocManager类管理。

 

CCommandLineInfo cmdInfo;

ParseCommandLine(cmdInfo);

if (!ProcessShellCommand(cmdInfo))

return FALSE;

接下来到了这几条语句,咋一看不知道是做什么的,其实正是这几条语句引起了窗口的创建等一系列的动作, 可以说是重中之重。让我们来看看这两条语句究竟做了什么:

MSDN说“MFC应用一般都会在它的应用对象中使用函数InitInstance创建CCommandLineInfo类的一个本地实例。然后把该对象传给CWinApp::ParseCommandLine,ParseCommandLine又重复调用CCommandLineInfo::ParseParam(每个参数调用一次)来填充CCommandLineInfo对象。最后,CCommandLineInfo对象被传给CWinApp::ProcessShellCommand来处理命令行参数和选项。“

实际上,这几条语句也确实是这么做的。首先定义了一个CCommandLineInfo类的实例,引发该类构造函数的调用,让我们看看这个构造函数的源代码:

CCommandLineInfo::CCommandLineInfo()

{

    m_bShowSplash = TRUE;

    m_bRunEmbedded = FALSE;

    m_bRunAutomated = FALSE;

    m_bRegisterPerUser = FALSE;

    m_nShellCommand = FileNew;

}

很明显,构造函数将m_nShellCommand默认为FileNew,它就是应用程序实例的Shell命令。

而后调用ParseCommandLine,看看这个函数做了什么:

void CWinApp::ParseCommandLine(CCommandLineInfo& rCmdInfo)

{

    for (inti = 1; i < __argc; i++)

    {

        LPCTSTR pszParam = __targv[i];

        BOOL bFlag = FALSE;

        BOOL bLast = ((i + 1) == __argc);

        if (pszParam[0] == '-' || pszParam[0] == '/')

        {

            // remove flag specifier

            bFlag = TRUE;

            ++pszParam;

        }

        rCmdInfo.ParseParam(pszParam, bFlag, bLast);

    }

}

可以看出这个函数的作用是对命令行参数的处理,确实如MSDN所说,处理后的每一个命令行参数都被送到ParseParam函数中。__argc和__argv[]的功能与控制台程序中的argc与argv类似,__argc代表着命令行参数的个数,__argv[]指向命令行参数字符串,__argc至少是1,而__argv[0] 指向程序文件所在的路径。所以先将pszParam的初值设为__argv[1],也就是指向第一个参数(如果存在的话),之后遍历各个命令行参数,如果在参数开头碰到”-”与”/”这些命令标志,跳过并将bFlag置为true,因为我们需要比对的只是去除标志之后的参数。另外,当解析到最后一个命令行参数的时候,将bLast也置为true。接下来看看ParseParam函数的源代码:

void CCommandLineInfo::ParseParam(const TCHAR*pszParam,BOOL bFlag,BOOL bLast)

{

    if (bFlag)

    {

        const CStringAstrParam(pszParam);

        ParseParamFlag(strParam.GetString());

    }

    else

        ParseParamNotFlag(pszParam);

    ParseLast(bLast);

}

如果前面遇到了带标志的命令行参数,那么将新建了一个CString对象,用来储存传进来的pszParam,然后调用ParseParamFlag进行处理:

命令行参数与执行的命令对应如下:

MFC再学习(一)单文档程序刨根究底_第3张图片

 再看下面的程序就很清楚了,其实就是将传进来的pszParam与各种参数进行比对,判断命令参数的类型。
void CCommandLineInfo::ParseParamFlag(const char* pszParam)

{

    // OLE command switches are caseinsensitive, while

    // shell command switches are casesensitive

    if (lstrcmpA(pszParam, "pt") == 0)

        m_nShellCommand = FilePrintTo;

    else if(lstrcmpA(pszParam, "p") == 0)

        m_nShellCommand = FilePrint;

    else if(::AfxInvariantStrICmp(pszParam, "Register")== 0 ||

        ::AfxInvariantStrICmp(pszParam, "Regserver")== 0)

        m_nShellCommand = AppRegister;

    else if(::AfxInvariantStrICmp(pszParam, "RegisterPerUser")== 0 ||

        ::AfxInvariantStrICmp(pszParam, "RegserverPerUser")== 0)

    {

        m_nShellCommand = AppRegister;

        m_bRegisterPerUser = TRUE;

    }

    else if(::AfxInvariantStrICmp(pszParam, "Unregister")== 0 ||

        ::AfxInvariantStrICmp(pszParam, "Unregserver")== 0)

        m_nShellCommand = AppUnregister;

    else if(::AfxInvariantStrICmp(pszParam, "UnregisterPerUser")== 0 ||

        ::AfxInvariantStrICmp(pszParam, "UnregserverPerUser")== 0)

    {

        m_nShellCommand = AppUnregister;

        m_bRegisterPerUser = TRUE;

    }

    else if(_strnicmp(pszParam, RESTART_COMMAND_LINE_ARG, _countof(RESTART_COMMAND_LINE_ARG)- 1) == 0)

    {

        CString strParam = pszParam;

        if (strParam.GetLength() ==_countof(RESTART_COMMAND_LINE_ARG) + RESTART_IDENTIFIER_LEN)

        {

            m_nShellCommand = RestartByRestartManager;

            m_strRestartIdentifier = strParam.Right(RESTART_IDENTIFIER_LEN);

        }

    }

    else if(lstrcmpA(pszParam, "ddenoshow")== 0)

    {

        AfxOleSetUserCtrl(FALSE);

        m_nShellCommand = FileDDENoShow;

    }

    else if(lstrcmpA(pszParam, "dde") == 0)

    {

        AfxOleSetUserCtrl(FALSE);

        m_nShellCommand = FileDDE;

    }

    else if(::AfxInvariantStrICmp(pszParam, "Embedding")== 0)

    {

        AfxOleSetUserCtrl(FALSE);

        m_bRunEmbedded = TRUE;

        m_bShowSplash = FALSE;

    }

    else if(::AfxInvariantStrICmp(pszParam, "Automation")== 0)

    {

        AfxOleSetUserCtrl(FALSE);

        m_bRunAutomated = TRUE;

        m_bShowSplash = FALSE;

    }

}

 

否则调用无标志参数的解析函数:

void CCommandLineInfo::ParseParamNotFlag(constTCHAR* pszParam)

{

    if (m_strFileName.IsEmpty())

        m_strFileName = pszParam;

    else if(m_nShellCommand == FilePrintTo && m_strPrinterName.IsEmpty())

        m_strPrinterName = pszParam;

    else if(m_nShellCommand == FilePrintTo && m_strDriverName.IsEmpty())

        m_strDriverName = pszParam;

    else if(m_nShellCommand == FilePrintTo && m_strPortName.IsEmpty())

        m_strPortName = pszParam;

}

 

接着调用CCommandLineInfo::ParseLast,源代码如下:

void CCommandLineInfo::ParseLast(BOOL bLast)

{

    if (bLast)

    {

        if (m_nShellCommand ==FileNew && !m_strFileName.IsEmpty())

            m_nShellCommand = FileOpen;

        m_bShowSplash = !m_bRunEmbedded && !m_bRunAutomated;

    }

}

这个函数的作用主要是在命令行参数全部处理完毕之后,判断参数中是否包含文件名,从而区分FileNew与FileOpen命令。(也就是上面表格中的第一种与第二种情况,还记得最开始m_nShellCommand最开始就被赋值为FileNew了么,如果参数不符合ParseParamFlag中任何一种情况,那么调用ParseLast时,m_nShellCommand就依然还是FileNew)

 

最后调用ProcessShellCommand(cmdInfo),这就是shell命令的解析函数了,看看代码便知道:

BOOL CWinApp::ProcessShellCommand(CCommandLineInfo&rCmdInfo)

{

    BOOL bResult = TRUE;

    switch (rCmdInfo.m_nShellCommand)

    {

……

    case CCommandLineInfo::FileNew:

        if(!AfxGetApp()->OnCmdMsg(ID_FILE_NEW, 0, NULL, NULL))

            OnFileNew();

        if (m_pMainWnd == NULL)

            bResult = FALSE;

        break;

……

}

这里只贴出FileNew部分的代码,很明显,这里就是分析m_nShellCommand的类型,通过OnCmdMsg函数来分发命令消息,最终由相应的命令响应函数进行响应。

还记得前面CCommandLineInfo类的构造函数中将m_nShellCommand默认赋值为FileNew了么,因为这里我们运行时,除了程序路径之外是没有命令行参数的,所以__argc就为1,CWinApp::ParseCommandLine实际上没有执行任何语句,实际起作用的只有ProcessShellCommand(cmdInfo),所以这里m_nShellCommand就等于FileNew,但是其他带有命令行参数情况下还是要经过CWinApp::ParseCommandLine进行解析的。

 

CWinApp类响应了这个命令消息:

void CWinApp::OnFileNew()

{

    if (m_pDocManager != NULL)

        m_pDocManager->OnFileNew();

}

而它又只是简单的调用了CDocManager类中的响应函数,还记得前面说的么,m_pDocManager正是CWinApp中指向CDocManager类的指针。

继续看源码:

void CDocManager::OnFileNew()

{

    if (m_templateList.IsEmpty())

    {

        TRACE(traceAppMsg, 0, "Error:no document templates registered with CWinApp.\n");

        AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);

        return;

    }

    CDocTemplate* pTemplate =(CDocTemplate*)m_templateList.GetHead();

    if (m_templateList.GetCount()> 1)

    {

        // more than one document templateto choose from

        // bring up dialog prompting user

        CNewTypeDlg dlg(&m_templateList);

        INT_PTR nID = dlg.DoModal();

        if (nID == IDOK)

            pTemplate = dlg.m_pSelectedTemplate;

        else

            return;     // none - canceloperation

    }

    ASSERT(pTemplate != NULL);

    ASSERT_KINDOF(CDocTemplate, pTemplate);

    pTemplate->OpenDocumentFile(NULL);

        // if returns NULL, the user hasalready been alerted

}

首先获得链表第一个节点的内容,也就是指向第一份文档模板的指针(至于为什么要做类型转换,是因为CPtrList类中定义链表结点DATA数据类型的时候,用的类型是void*,巧妙的实现了泛型的效果,因为MFC编写的时候template还没有成为标准。根据类型转换的不同,就成为了不同的类型)。然后判断m_templateList中的结点数,也就是相应的文档模板数,大于1说明有多个文档模板,则会生成一个模态对话框询问我们是否要选择模板。默认的话只生成了一种文档模板,所以直接调用OpenDocumentFile,因为这是个纯虚函数

virtual CDocument* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible =TRUE) = 0;

virtual CDocument* OpenDocumentFile(LPCTSTR lpszPathName, BOOL bAddToMRU, BOOLbMakeVisible) = 0;

而子类CSingleDocTemplate重载了这个函数,所以调用的是CSingleDocTemplate::OpenDocumentFile。

源代码如下:

CDocument*CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bMakeVisible)

{

    returnOpenDocumentFile(lpszPathName, TRUE, bMakeVisible);

}

 

CDocument*CSingleDocTemplate::OpenDocumentFile(LPCTSTR lpszPathName, BOOL bAddToMRU, BOOLbMakeVisible)

{

    CDocument* pDocument = NULL;

    CFrameWnd* pFrame = NULL;

    BOOL bCreated = FALSE;     // => doc and frame created

    BOOL bWasModified = FALSE;

    if (m_pOnlyDoc != NULL)

    {

        // already have a document - reinitit

        pDocument = m_pOnlyDoc;

        if(!pDocument->SaveModified())

        {

            // set a flag to indicate thatthe document being opened should not

            // be removed from the MRUlist, if it was being opened from there

            g_bRemoveFromMRU = FALSE;

            return NULL;        // leave theoriginal one

        }

        pFrame = (CFrameWnd*)AfxGetMainWnd();

        ASSERT(pFrame != NULL);

        ASSERT_KINDOF(CFrameWnd, pFrame);

        ASSERT_VALID(pFrame);

    }

    else

    {

        // create a new document

        pDocument = CreateNewDocument();

        ASSERT(pFrame == NULL);    // will be created below

        bCreated = TRUE;

    }

    if (pDocument == NULL)

    {

        AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);

        return NULL;

    }

    ASSERT(pDocument == m_pOnlyDoc);

    if (pFrame == NULL)

    {

        ASSERT(bCreated);

 

        // create frame - set as maindocument frame

        BOOL bAutoDelete = pDocument->m_bAutoDelete;

        pDocument->m_bAutoDelete = FALSE;

                    // don't destroy ifsomething goes wrong

        pFrame = CreateNewFrame(pDocument,NULL);

        pDocument->m_bAutoDelete = bAutoDelete;

        if (pFrame == NULL)

        {

            AfxMessageBox(AFX_IDP_FAILED_TO_CREATE_DOC);

            delete pDocument;      // explicitdelete on error

            return NULL;

        }

    }

    if (lpszPathName == NULL)//调用OpenDocumentFile时传进来的实参是NULL

    {

        // create a new document

        SetDefaultTitle(pDocument);

        // avoid creating temporarycompound file when starting up invisible

        if (!bMakeVisible)

            pDocument->m_bEmbedded = TRUE;

        if (!pDocument->OnNewDocument())

        {

            // user has been alerted towhat failed in OnNewDocument

            TRACE(traceAppMsg, 0, "CDocument::OnNewDocumentreturned FALSE.\n");

            if (bCreated)

                pFrame->DestroyWindow();    // will destroydocument

            return NULL;

        }

    }

    else

    {

        CWaitCursor wait;

        // open an existing document

        bWasModified = pDocument->IsModified();

        pDocument->SetModifiedFlag(FALSE);  // not dirty foropen

        if (!pDocument->OnOpenDocument(lpszPathName))

        {

            // user has been alerted towhat failed in OnOpenDocument

            TRACE(traceAppMsg, 0, "CDocument::OnOpenDocumentreturned FALSE.\n");

            if (bCreated)

            {

                pFrame->DestroyWindow();    // will destroydocument

            }

            else if (!pDocument->IsModified())

            {

                // original document isuntouched

                pDocument->SetModifiedFlag(bWasModified);

            }

            else

            {

                // we corrupted theoriginal document

                SetDefaultTitle(pDocument);

 

                if(!pDocument->OnNewDocument())

                {

                    TRACE(traceAppMsg, 0, "Error:OnNewDocument failed after trying "

                        "to open adocument - trying to continue.\n");

                    // assume we cancontinue

                }

            }

            return NULL;        // openfailed

        }

        pDocument->SetPathName(lpszPathName, bAddToMRU);

        pDocument->OnDocumentEvent(CDocument::onAfterOpenDocument);

    }

 

    CWinThread* pThread = AfxGetThread();

    ASSERT(pThread);

    if (bCreated &&pThread->m_pMainWnd == NULL)

    {

        // set as main frame(InitialUpdateFrame will show the window)

        pThread->m_pMainWnd = pFrame;

    }

    InitialUpdateFrame(pFrame, pDocument,bMakeVisible);

    return pDocument;

}

因为m_pOnlyDoc 等于NULL,接下来调用基类的CreateNewDocument(),代码如下:

    CDocument*CDocTemplate::CreateNewDocument()

{

    // default implementation constructsone from CRuntimeClass

    if (m_pDocClass == NULL)

    {

        TRACE(traceAppMsg, 0, "Error:you must override CDocTemplate::CreateNewDocument.\n");

        ASSERT(FALSE);

        return NULL;

    }

    CDocument* pDocument =(CDocument*)m_pDocClass->CreateObject();

    if (pDocument == NULL)

    {

        TRACE(traceAppMsg, 0, "Warning:Dynamic create of document type %hs failed.\n",

            m_pDocClass->m_lpszClassName);

        return NULL;

    }

    ASSERT_KINDOF(CDocument, pDocument);

    AddDocument(pDocument);

    return pDocument;

}

这里通过m_pDocClass(不记得了?它等于文档类的CRunTimeClass指针)调用CreateObject()动态创建了文档,然后通过调用AddDocument函数使新创建的文档与文档模板产生关联。

首先调用的是子类的CSingleDocTemplate::AddDocument,

void CSingleDocTemplate::AddDocument (CDocument* pDoc)

{

    ASSERT(m_pOnlyDoc == NULL);    // one at a time please

    ASSERT_VALID(pDoc);

    CDocTemplate::AddDocument(pDoc);

    m_pOnlyDoc = pDoc;

}

里面又调用了基类的CDocTemplate::AddDocument,使创建的新文档回指其所属的文档模板

void CDocTemplate::AddDocument(CDocument* pDoc)

{

    ASSERT_VALID(pDoc);

    ASSERT(pDoc->m_pDocTemplate == NULL);   // no templateattached yet

    pDoc->m_pDocTemplate = this;

}

最后,将m_ pOnlyDoc指向这唯一的文档。

 

接下来,继续回到CSingleDocTemplate::OpenDocumentFile执行,CreateNewFrame被调用:

CFrameWnd*CDocTemplate::CreateNewFrame(CDocument* pDoc, CFrameWnd* pOther)

{

    if (pDoc != NULL)

        ASSERT_VALID(pDoc);

    // create a frame wired to thespecified document

 

    ASSERT(m_nIDResource != 0); // musthave a resource ID to load from

    CCreateContext context;

    context.m_pCurrentFrame = pOther;

    context.m_pCurrentDoc = pDoc;

    context.m_pNewViewClass = m_pViewClass;

    context.m_pNewDocTemplate = this;

 

    if (m_pFrameClass == NULL)

    {

        TRACE(traceAppMsg, 0, "Error:you must override CDocTemplate::CreateNewFrame.\n");

        ASSERT(FALSE);

        return NULL;

    }

    CFrameWnd* pFrame = (CFrameWnd*)m_pFrameClass->CreateObject();

    if (pFrame == NULL)

    {

        TRACE(traceAppMsg, 0, "Warning:Dynamic create of frame %hs failed.\n",

            m_pFrameClass->m_lpszClassName);

        return NULL;

    }

    ASSERT_KINDOF(CFrameWnd, pFrame);

    if (context.m_pNewViewClass ==NULL)

        TRACE(traceAppMsg, 0, "Warning:creating frame with no default view.\n");

    // create new from resource

    if (!pFrame->LoadFrame(m_nIDResource,

            WS_OVERLAPPEDWINDOW |FWS_ADDTOTITLE,   // default frame styles

            NULL, &context))

    {

        TRACE(traceAppMsg, 0, "Warning:CDocTemplate couldn't create a frame.\n");

        // frame will be deleted inPostNcDestroy cleanup

        return NULL;

    }

    // it worked !

    return pFrame;

}

这里就是《深入浅出MFC 第二版》 p464的内容了,视图的创建是在框架窗口生成前(LoadFrame中)被引发的,显得比较隐晦。其中CCreateContext类就包含了创建视图时所需要的必要参数,m_pNewViewClass就是待创建视图对象的CRunTimeClass指针,用于动态创建。其余的则是与待创建视图对象所关联的文档,框架,文档模板。

同样的调用CreateObject()动态创建出框架类对象后终于到了我们熟悉的LoadFrame函数(和MDI不同,SDI程序并未重载这个函数,所以直接调用基类的)。

调用情况如下:

LoadFrame(m_nIDResource,WS_OVERLAPPEDWINDOW | FWS_ADDTOTITLE,&context);

定义如下:

BOOLCFrameWnd::LoadFrame(UINT nIDResource, DWORD dwDefaultStyle,

    CWnd* pParentWnd, CCreateContext* pContext)

{

    // only do this once

    ASSERT_VALID_IDR(nIDResource);

    ASSERT(m_nIDHelp == 0 || m_nIDHelp == nIDResource);

 

    m_nIDHelp = nIDResource;   // ID for help context (+HID_BASE_RESOURCE)

 

    CString strFullString;

    if(strFullString.LoadString(nIDResource))

        AfxExtractSubString(m_strTitle, strFullString, 0);    // firstsub-string

    VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));

    // attempt to create the window

    LPCTSTR lpszClass = GetIconWndClass(dwDefaultStyle,nIDResource);

    CString strTitle = m_strTitle;

    if (!Create(lpszClass,strTitle, dwDefaultStyle, rectDefault,

     pParentWnd, ATL_MAKEINTRESOURCE(nIDResource), 0L, pContext))

    {

        return FALSE;   // will selfdestruct on failure normally

    }

    // save the default menu handle

    ASSERT(m_hWnd != NULL);

    m_hMenuDefault = m_dwMenuBarState == AFX_MBS_VISIBLE ? ::GetMenu(m_hWnd) : m_hMenu;

    // load accelerator resource

    LoadAccelTable(ATL_MAKEINTRESOURCE(nIDResource));

    if (pContext == NULL)   // send initialupdate

        SendMessageToDescendants(WM_INITIALUPDATE,0, 0, TRUE, TRUE);

    return TRUE;

}

LoadFrame是个很重要的函数,窗口类的注册,创建都在这里完成,在这里面最终就能看到被层层包装的WIN32 API。首先,利用AfxDeferRegisterClass对窗口进行第一次注册,而AfxDeferRegisterClass是个宏:

#define AfxDeferRegisterClass(fClass)  AfxEndDeferRegisterClass(fClass)

实际调用的是AfxEndDeferRegisterClass:

BOOL AFXAPIAfxEndDeferRegisterClass(LONG fToRegister)

{

    // mask off all classes that arealready registered

    AFX_MODULE_STATE* pModuleState = AfxGetModuleState();

    fToRegister &= ~pModuleState->m_fRegisteredClasses;

    if (fToRegister == 0)

        return TRUE;

 

    LONG fRegisteredClasses = 0;

 

    // common initialization

    WNDCLASS wndcls;

    memset(&wndcls, 0, sizeof(WNDCLASS));  // start with NULLdefaults

    wndcls.lpfnWndProc = DefWindowProc;

    wndcls.hInstance = AfxGetInstanceHandle();

    wndcls.hCursor = afxData.hcurArrow;

 

    INITCOMMONCONTROLSEX init;

    init.dwSize = sizeof(init);

 

    // work to register classes asspecified by fToRegister, populate fRegisteredClasses as we go

    ……//省略

        if(fToRegister & AFX_WNDFRAMEORVIEW_REG)

    {

        // SDI Frame or MDI Child windowsor views - normal colors

        wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;

        wndcls.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);

        if (_AfxRegisterWithIcon(&wndcls,_afxWndFrameOrView, AFX_IDI_STD_FRAME))

            fRegisteredClasses |= AFX_WNDFRAMEORVIEW_REG;

    }

……//省略

   return (fToRegister & fRegisteredClasses) == fToRegister;

}

MFC为我们预设了许多窗口风格,这里默认的是AFX_WNDFRAMEORVIEW_REG,通过判断传进来的窗口风格,将窗口初始化为相应的风格(这里为wndcls.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW)。另外还为窗口初始化了默认消息处理程序,句柄,光标等。

初始化完成之后,调用了_AfxRegisterWithIcon:

AFX_STATIC BOOL AFXAPI_AfxRegisterWithIcon(WNDCLASS* pWndCls,

    LPCTSTR lpszClassName, UINT nIDIcon)

{

    pWndCls->lpszClassName = lpszClassName;

    HINSTANCE hInst = AfxFindResourceHandle(

        ATL_MAKEINTRESOURCE(nIDIcon), ATL_RT_GROUP_ICON);

    if ((pWndCls->hIcon =::LoadIconW(hInst, ATL_MAKEINTRESOURCEW(nIDIcon))) == NULL)

    {

        // use default icon

        pWndCls->hIcon = ::LoadIcon(NULL, IDI_APPLICATION);

    }

    return AfxRegisterClass(pWndCls);

}

可以看到这里先为窗口加载了默认图标,最后调用AfxRegisterClass,源代码如下:

BOOL AFXAPI AfxRegisterClass(WNDCLASS* lpWndClass)

{

    WNDCLASS wndcls;       

    if (AfxCtxGetClassInfo(lpWndClass->hInstance,lpWndClass->lpszClassName,

        &wndcls))

    {

        // class already registered

        return TRUE;

    }

    if (!::AfxCtxRegisterClass(lpWndClass))

    {

        TRACE(traceAppMsg, 0, _T("Can'tregister window class named %s\n"),

            lpWndClass->lpszClassName);

        return FALSE;

    }

    BOOL bRet = TRUE;

 

    if (afxContextIsDLL)

    {

        AfxLockGlobals(CRIT_REGCLASSLIST);

        TRY

        {

            // class registeredsuccessfully, add to registered list

            AFX_MODULE_STATE* pModuleState = AfxGetModuleState();

            pModuleState->m_strUnregisterList+=lpWndClass->lpszClassName;

            pModuleState->m_strUnregisterList+='\n';

        }

        CATCH_ALL(e)

        {

            AfxUnlockGlobals(CRIT_REGCLASSLIST);

            THROW_LAST();

            // Note: DELETE_EXCEPTION notrequired.

        }

        END_CATCH_ALL

        AfxUnlockGlobals(CRIT_REGCLASSLIST);

    }

    return bRet;

}

函数会先调用AfxCtxGetClassInfo判断窗口是否已经被注册,如果未注册,接着调用::AfxCtxRegisterClass(lpWndClass),其中又会调用:: RegisterClass(lpWndClass)完成窗口注册。到了这里终于看到隐藏很深的API函数了。

接下来就要准备创建窗口了,注册完成后遇到的第一个函数是GetIconWndClass,

LPCTSTRCFrameWnd::GetIconWndClass(DWORD dwDefaultStyle, UINT nIDResource)

{

    ASSERT_VALID_IDR(nIDResource);

    HINSTANCE hInst = AfxFindResourceHandle(

        ATL_MAKEINTRESOURCE(nIDResource), ATL_RT_GROUP_ICON);

    HICON hIcon = ::LoadIconW(hInst,ATL_MAKEINTRESOURCEW(nIDResource));

    if (hIcon != NULL)

    {

        CREATESTRUCT cs;

        memset(&cs, 0, sizeof(CREATESTRUCT));

        cs.style = dwDefaultStyle;

        PreCreateWindow(cs);

            // will fill lpszClassName withdefault WNDCLASS name

            // ignore instance handle fromPreCreateWindow.

 

        WNDCLASS wndcls;

        if (cs.lpszClass != NULL&&

            AfxCtxGetClassInfo(AfxGetInstanceHandle(),cs.lpszClass, &wndcls) &&

            wndcls.hIcon != hIcon)

        {

            // register a very similarWNDCLASS

            return AfxRegisterWndClass(wndcls.style,

                wndcls.hCursor, wndcls.hbrBackground, hIcon);

        }

    }

    return NULL;        // just usethe default

}

这里首先调用了PreCreateWindow,因为CMainFrame是CFrameWndEx的子类(MFC9.0新增?),CFrameWndEx又是CFrameWnd的子类,所以调用的是CMainFrame::PreCreateWindow。这个函数主要给我们提供了一个机会,能够在窗口生成之前修改窗口的样式,sounds nice,hmm?

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

        if(!CFrameWndEx::PreCreateWindow(cs) )

            return FALSE;

        // TODO: 在此处通过修改

        //  CREATESTRUCT cs 来修改窗口类或样式

        return TRUE;

}

首先,调用其基类的CFrameWndEx::PreCreateWindow,

BOOLCFrameWndEx::PreCreateWindow(CREATESTRUCT& cs)

{

    m_dockManager.Create(this);

    m_Impl.SetDockingManager(&m_dockManager);

 

    m_Impl.RestorePosition(cs);

    return CFrameWnd::PreCreateWindow(cs);

}

这里首先创建了dockManager,然后继续调用了基类的CFrameWnd::PreCreateWindow,

BOOLCFrameWnd::PreCreateWindow(CREATESTRUCT& cs)

{

    if (cs.lpszClass == NULL)

    {

        VERIFY(AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG));

        cs.lpszClass = _afxWndFrameOrView;  // COLOR_WINDOW background

    }

    if (cs.style &FWS_ADDTOTITLE)

        cs.style |= FWS_PREFIXTITLE;

    cs.dwExStyle |= WS_EX_CLIENTEDGE;

    return TRUE;

}

这个函数必须在创建窗口前调用,用来完成对窗口风格参数的预处理。

 

CMainFrame::PreCreateWindow调用结束回到GetIconWndClass函数,接着调用AfxCtxGetClassInfo函数重新收集cs信息(因为前面CMainFrame::PreCreateWindow中可以更改cs),并调用AfxRegisterWndClass函数第二次注册窗口(返回lpszName并赋值给lpszClass,再将lpszClass传递给create,也就是GetIconWndClass调用结束时,lpszClass就不为空了),至此窗口才算是真正的注册完毕。

接下来就回到了LoadFrame函数,调用Create(lpszClass, strTitle, dwDefaultStyle, rectDefault,pParentWnd,ATL_MAKEINTRESOURCE(nIDResource), 0L, pContext)来创建窗口。

函数定义如下:

BOOL CFrameWnd::Create(LPCTSTR lpszClassName,

    LPCTSTR lpszWindowName,

    DWORD dwStyle,

    const RECT& rect,

    CWnd* pParentWnd,

    LPCTSTR lpszMenuName,

    DWORD dwExStyle,

    CCreateContext* pContext)

{

    HMENU hMenu = NULL;

    if (lpszMenuName != NULL)

    {

        // load in a menu that will getdestroyed when window gets destroyed

        HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName,ATL_RT_MENU);

        if ((hMenu =::LoadMenu(hInst, lpszMenuName)) == NULL)

        {

            TRACE(traceAppMsg, 0, "Warning:failed to load menu for CFrameWnd.\n");

            PostNcDestroy();            //perhaps delete the C++ object

            return FALSE;

        }

    }

    m_strTitle = lpszWindowName;   // save title for later

    if (!CreateEx(dwExStyle,lpszClassName, lpszWindowName, dwStyle,

        rect.left, rect.top, rect.right -rect.left, rect.bottom - rect.top,

        pParentWnd->GetSafeHwnd(), hMenu,(LPVOID)pContext))

    {

        TRACE(traceAppMsg, 0, "Warning:failed to create CFrameWnd.\n");

        if (hMenu != NULL)

            DestroyMenu(hMenu);

        returnFALSE;

    }

    return TRUE;

}

包括从LoadFrame传递进来的4个参数UINT nIDResource, DWORDdwDefaultStyle,CWnd* pParentWnd, CCreateContext* pContext,所有的参数都在调用CFrameWnd::Create之前初始化完毕,传递给create函数。Create在调用CreateEx之前,又对参数做了一些必要的处理(如获得菜单的句柄,因为参数列表并不是完全一致),再将参数传递给了CreateEx。

CreateEx的定义如下:

BOOL CWnd::CreateEx(DWORDdwExStyle, LPCTSTR lpszClassName,

    LPCTSTR lpszWindowName, DWORD dwStyle,

    int x, inty, int nWidth, intnHeight,

    HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)

{

    ASSERT(lpszClassName == NULL || AfxIsValidString(lpszClassName)||

        AfxIsValidAtom(lpszClassName));

    ENSURE_ARG(lpszWindowName == NULL ||AfxIsValidString(lpszWindowName));

   

    // allow modification of several commoncreate parameters

    CREATESTRUCT cs;

    cs.dwExStyle = dwExStyle;

    cs.lpszClass = lpszClassName;

    cs.lpszName = lpszWindowName;

    cs.style = dwStyle;

    cs.x = x;

    cs.y = y;

    cs.cx = nWidth;

    cs.cy = nHeight;

    cs.hwndParent = hWndParent;

    cs.hMenu = nIDorHMenu;

    cs.hInstance = AfxGetInstanceHandle();

    cs.lpCreateParams = lpParam;

    if (!PreCreateWindow(cs))

    {

        PostNcDestroy();

        return FALSE;

    }

    AfxHookWindowCreate(this);

    HWND hWnd = ::AfxCtxCreateWindowEx(cs.dwExStyle, cs.lpszClass,

            cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,

            cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);

#ifdef _DEBUG

    if (hWnd == NULL)

    {

        TRACE(traceAppMsg, 0, "Warning:Window creation failed: GetLastError returns 0x%8.8X\n",

            GetLastError());

    }

#endif

    if (!AfxUnhookWindowCreate())

        PostNcDestroy();       // cleanup if CreateWindowEx fails too soon

    if (hWnd == NULL)

        return FALSE;

    ASSERT(hWnd == m_hWnd); // should havebeen set in send msg hook

    return TRUE;

}

首先定义了一个CREATESTRUCT结构体对象cs,并用传递进来的参数填充它,cs等会就将用来创建窗口。 接下来又一次的调用了PreCreateWindow,但是此时lpszClass已经不为空(原因前面说了),所以AfxDeferRegisterClass(AFX_WNDFRAMEORVIEW_REG)并不会被调用。之后调用AfxHookWindowCreate(this)设置钩子和过滤函数,用于替换默认的窗口过程函数,这里不细说。

最后,::AfxCtxCreateWindowEx调用::CreateWindowEx执行真正的窗口创建动作,并在函数返回前发送出WM_CREATE消息。这个消息会被系统送往窗口过程AfxWndProc处,然后经由消息映射,会首先在CMainFrame类中找到响应函数CMainFrame::OnCreate,来看看它的定义:

int CMainFrame::OnCreate(LPCREATESTRUCTlpCreateStruct)

{

    if (CFrameWndEx::OnCreate(lpCreateStruct)== -1)

        return -1;

 

    BOOL bNameValid;

    // 基于持久值设置视觉管理器和样式

    OnApplicationLook(theApp.m_nAppLook);

 

    if (!m_wndMenuBar.Create(this))

    {

        TRACE0("未能创建菜单栏\n");

        return-1;      // 未能创建

    }

 

    m_wndMenuBar.SetPaneStyle(m_wndMenuBar.GetPaneStyle()| CBRS_SIZE_DYNAMIC | CBRS_TOOLTIPS | CBRS_FLYBY);

 

    // 防止菜单栏在激活时获得焦点

    CMFCPopupMenu::SetForceMenuFocus(FALSE);

 

    if (!m_wndToolBar.CreateEx(this,TBSTYLE_FLAT, WS_CHILD | WS_VISIBLE | CBRS_TOP | CBRS_GRIPPER | CBRS_TOOLTIPS |CBRS_FLYBY | CBRS_SIZE_DYNAMIC) ||

        !m_wndToolBar.LoadToolBar(theApp.m_bHiColorIcons? IDR_MAINFRAME_256 : IDR_MAINFRAME))

    {

        TRACE0("未能创建工具栏\n");

        return -1;      // 未能创建

    }

 

    CString strToolBarName;

    bNameValid =strToolBarName.LoadString(IDS_TOOLBAR_STANDARD);

    ASSERT(bNameValid);

    m_wndToolBar.SetWindowText(strToolBarName);

 

    CString strCustomize;

    bNameValid =strCustomize.LoadString(IDS_TOOLBAR_CUSTOMIZE);

    ASSERT(bNameValid);

    m_wndToolBar.EnableCustomizeButton(TRUE,ID_VIEW_CUSTOMIZE, strCustomize);

 

    // 允许用户定义的工具栏操作:

    InitUserToolbars(NULL, uiFirstUserToolBarId,uiLastUserToolBarId);

 

    if (!m_wndStatusBar.Create(this))

    {

        TRACE0("未能创建状态栏\n");

        return -1;      // 未能创建

    }

    m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT));

 

    // TODO: 如果您不希望工具栏和菜单栏可停靠,请删除这五行

    m_wndMenuBar.EnableDocking(CBRS_ALIGN_ANY);

    m_wndToolBar.EnableDocking(CBRS_ALIGN_ANY);

    EnableDocking(CBRS_ALIGN_ANY);

    DockPane(&m_wndMenuBar);

    DockPane(&m_wndToolBar);

 

    // 启用 Visual Studio 2005 样式停靠窗口行为

    CDockingManager::SetDockingMode(DT_SMART);

    // 启用 Visual Studio 2005 样式停靠窗口自动隐藏行为

    EnableAutoHidePanes(CBRS_ALIGN_ANY);

 

    // 启用工具栏和停靠窗口菜单替换

    EnablePaneMenu(TRUE, ID_VIEW_CUSTOMIZE,strCustomize, ID_VIEW_TOOLBAR);

 

    // 启用快速(按住 Alt 拖动)工具栏自定义

    CMFCToolBar::EnableQuickCustomization();

 

    if (CMFCToolBar::GetUserImages() == NULL)

    {

        // 加载用户定义的工具栏图像

        if(m_UserImages.Load(_T(".\\UserImages.bmp")))

        {

            CMFCToolBar::SetUserImages(&m_UserImages);

        }

    }

 

    // 启用菜单个性化(最近使用的命令)

    // TODO: 定义您自己的基本命令,确保每个下拉菜单至少有一个基本命令。

    CList<UINT, UINT> lstBasicCommands;

 

    lstBasicCommands.AddTail(ID_FILE_NEW);

    lstBasicCommands.AddTail(ID_FILE_OPEN);

    lstBasicCommands.AddTail(ID_FILE_SAVE);

    lstBasicCommands.AddTail(ID_FILE_PRINT);

    lstBasicCommands.AddTail(ID_APP_EXIT);

    lstBasicCommands.AddTail(ID_EDIT_CUT);

    lstBasicCommands.AddTail(ID_EDIT_PASTE);

    lstBasicCommands.AddTail(ID_EDIT_UNDO);

    lstBasicCommands.AddTail(ID_APP_ABOUT);

    lstBasicCommands.AddTail(ID_VIEW_STATUS_BAR);

    lstBasicCommands.AddTail(ID_VIEW_TOOLBAR);

    lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2003);

    lstBasicCommands.AddTail(ID_VIEW_APPLOOK_VS_2005);

    lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_BLUE);

    lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_SILVER);

    lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_BLACK);

    lstBasicCommands.AddTail(ID_VIEW_APPLOOK_OFF_2007_AQUA);

    lstBasicCommands.AddTail(ID_VIEW_APPLOOK_WINDOWS_7);

    CMFCToolBar::SetBasicCommands(lstBasicCommands);

    return 0;

}

首先调用了基类的CFrameWndEx::OnCreate,

int CFrameWndEx::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

    if (CFrameWnd::OnCreate(lpCreateStruct)== -1)

        return -1;

 

    m_Impl.m_bHasBorder= (lpCreateStruct->style & WS_BORDER) != NULL;

 

    CFrameImpl::AddFrame(this);

    OnChangeVisualManager(0,0);

    return 0;

}

继续调用基类的CFrameWnd::OnCreate,

 CFrameWnd::OnCreate(LPCREATESTRUCT lpcs)

{

    ENSURE_ARG(lpcs != NULL);

    CCreateContext* pContext =(CCreateContext*)lpcs->lpCreateParams;

    return OnCreateHelper(lpcs,pContext);

}

接着OnCreateHelper(lpcs,pContext)被调用,

int CFrameWnd::OnCreateHelper(LPCREATESTRUCT lpcs, CCreateContext* pContext)

{

    if (CWnd::OnCreate(lpcs)== -1)

        return -1;

    // create special children first

    if (!OnCreateClient(lpcs,pContext))

    {

        TRACE(traceAppMsg, 0, "Failedto create client pane/view for frame.\n");

        return -1;

    }

    // post message for initial messagestring

    PostMessage(WM_SETMESSAGESTRING,AFX_IDS_IDLEMESSAGE);

    // make sure the child windows havebeen properly sized

    RecalcLayout();

    return 0;   // create ok

}

接着CWnd::OnCreate被调用,该函数需在::CreateEX前被调用,为衍生类做一些必要的初始化工作,返回为0时表示正常,为-1时表示出错,需要销毁窗口。

接下来继续调用OnCreateClient创建客户区进而引发CreateView的调用:

BOOLCFrameWnd::OnCreateClient(LPCREATESTRUCT, CCreateContext* pContext)

{

    // default create client will create aview if asked for it

    if (pContext != NULL &&pContext->m_pNewViewClass != NULL)

    {

        if (CreateView(pContext,AFX_IDW_PANE_FIRST)== NULL)

            return FALSE;

    }

    return TRUE;

}

 

CWnd*CFrameWnd::CreateView(CCreateContext* pContext, UINT nID)

{

    ASSERT(m_hWnd != NULL);

    ASSERT(::IsWindow(m_hWnd));

    ENSURE_ARG(pContext != NULL);

    ENSURE_ARG(pContext->m_pNewViewClass != NULL);

    // Note: can be a CWnd withPostNcDestroy self cleanup

    CWnd* pView =(CWnd*)pContext->m_pNewViewClass->CreateObject();

    if (pView == NULL)

    {

        TRACE(traceAppMsg, 0, "Warning:Dynamic create of view type %hs failed.\n",

            pContext->m_pNewViewClass->m_lpszClassName);

        return NULL;

    }

    ASSERT_KINDOF(CWnd, pView);

    // views are always created with aborder!

    if (!pView->Create(NULL,NULL, AFX_WS_DEFAULT_VIEW,

        CRect(0,0,0,0), this, nID, pContext))

    {

        TRACE(traceAppMsg, 0, "Warning:could not create view for frame.\n");

        return NULL;        // can'tcontinue without a view

    }

    if (pView->GetExStyle() &WS_EX_CLIENTEDGE)

    {

        // remove the 3d style from theframe, since the view is

        // providing it.

        // make sure to recalc thenon-client area

        ModifyStyleEx(WS_EX_CLIENTEDGE, 0, SWP_FRAMECHANGED);

    }

    return pView;

}

可以看到,在CreateView中,有一句视图对象先被动态创建出来了。而后通过pView调用Create创建视图窗口,创建过程与创建框架窗口类似,CView::Create-->CWnd:: Create-->CWnd::CreateEx--> ::AfxCtxCreateWindowEx-->::CreateWindowEx,同时发出WM_CREATE消息,这里在CView类中响应了这个消息,引发CView::OnCreate的调用

int CView::OnCreate(LPCREATESTRUCT lpcs)

{

    if (CWnd::OnCreate(lpcs) == -1)

        return -1;

    // if ok, wire in the current document

    ASSERT(m_pDocument == NULL);

    CCreateContext* pContext =(CCreateContext*)lpcs->lpCreateParams;

    // A view should be created in a givencontext!

    if (pContext != NULL &&pContext->m_pCurrentDoc != NULL)

    {

        pContext->m_pCurrentDoc->AddView(this);

        ASSERT(m_pDocument != NULL);

    }

    else

    {

        TRACE(traceAppMsg, 0, "Warning:Creating a pane with no CDocument.\n");

    }

    return 0;   // ok

}

这里主要是调用了AddView将行创建的CView对象指针pView加入到CDocument类的m_viewList中,另一方面又让CView对象回指其所属的文档。m_viewList类似于m_templateList,是由文档类管理的视类对象链表。这样就建立起了两者之间的联系。

void CDocument::AddView(CView* pView)

{

    ASSERT_VALID(pView);

    ASSERT(pView->m_pDocument == NULL); //must not be already attached

    ASSERT(m_viewList.Find(pView, NULL) == NULL);   // must not be inlist

    m_viewList.AddTail(pView);

    ASSERT(pView->m_pDocument == NULL); //must be un-attached

    pView->m_pDocument = this;

    OnChangedViewList();    // must be thelast thing done to the document

}

最后调用OnChangedViewList用来完成m_viewList发生变动之后的工作,如果m_viewList为空,则删除文档。并且更新框架数。

之后回到OnCreateHelper中,发送消息让框架显示信息,最后调用RecalcLayout()。

函数声明如下:

virtual void RecalcLayout(BOOL bNotify =TRUE );

可以看到默认的参数为true,并且CView中没有重载这个函数,最终调用的是CFrameWnd::RecalcLayout。根据MSDN,当框架窗口的外观发生改变时,这个函数会被用来控制control bars(也就是状态栏,工具栏等的总称)的行为与外观(具体来说是调用CWnd::RepositionBars函数来重新摆放control bars),并且当bNotify参数为true时,controlbars将会接收通知消息。

至此,CFrameWnd::OnCreate的调用过程结束,回到CFrameWndEX::OnCreate中,先后调用AddFrame(this)与OnChangeVisualManager(0, 0)之后返回到CMainFrame::OnCreate中,

调用m_wndMenuBar.Create创建菜单栏,并调用m_wndMenuBar.SetPaneStyle设置菜单栏窗格样式。m_wndToolBar.CreateEx和m_wndToolBar.LoadToolBar创建或者加载现有工具栏资源,并为其设定好名称。而后调用m_wndStatusBar.Create创建状态栏。而后调用m_wndStatusBar.SetIndicators(indicators,sizeof(indicators)/sizeof(UINT));来设置状态栏指示器,也就在我们程序右下角状态栏的显示。indicators参数就是状态栏指示器数组的地址,如果我们想要改变状态栏的显示,就应该改变indicators的数组成员。

static UINTindicators[] =

{

    ID_SEPARATOR,           // 状态行指示器

    ID_INDICATOR_CAPS,

    ID_INDICATOR_NUM,

    ID_INDICATOR_SCRL,

};

剩下的那些函数的功能,注释已经说得很清楚了,这里不再赘述。

至此,CMainFrame::OnCreate函数的调用过程就结束了,同时Create函数也将返回,回到Loadframe函数继续执行。在调用CFrameWnd::LoadAccelTable加载加速键表之后,调用 SendMessageToDescendants(WM_INITIALUPDATE, 0, 0, TRUE, TRUE)给子窗口发送WM_INITIALUPDATE消息,CView类将响应这个消息用来完成窗口内容的初始化工作(如设置为重绘区域,清除背景)。

void CView::OnInitialUpdate()

{

    OnUpdate(NULL, 0, NULL);       //initial update

}

void CView::OnUpdate(CView* pSender, LPARAM/*lHint*/,CObject*/*pHint*/)

{

    ASSERT(pSender != this);

    UNUSED(pSender);     // unused in release builds

 

    // invalidate the entire pane, erasebackground too

    Invalidate(TRUE);

}

Loadframe函数的调用过程终于结束,回到CreateNewFrame函数中,在返回框架对象的指针之后,又回到了OpenDocumentFile函数继续执行。因为调用OpenDocumentFile时传进来的实参是NULL,所以lpszPathName等于NULL,接下来调用

pDocument->OnNewDocument(lpszPathName),定义如下:

BOOLCDocument::OnNewDocument()

{

#ifdef _DEBUG

    if(IsModified())

        TRACE(traceAppMsg, 0, "Warning:OnNewDocument replaces an unsaved document.\n");

#endif

    DeleteContents();

    m_strPathName.Empty();     // no path name yet

    SetModifiedFlag(FALSE);    // make clean

    OnDocumentEvent(onAfterNewDocument);

    return TRUE;

}

这里是对文档对象做一些初始化工作(如清空内容与路径名等),最后调用了OnDocumentEvent函数,用于处理文档事件,这里处理的是新建文档产生的事件。根据MSDN,调用这个函数的原因主要是文档事件会对CDocument之外的类也产生影响,需要在这个函数中进行处理。

 

接下来调用InitialUpdateFrame(pFrame,pDocument, bMakeVisible)对框架窗口内容也进行必要的初始化,函数定义如下:

voidCDocTemplate::InitialUpdateFrame(CFrameWnd* pFrame, CDocument* pDoc,BOOLbMakeVisible)

{

       // just delagate to implementation inCFrameWnd

       pFrame->InitialUpdateFrame(pDoc,bMakeVisible);

}

而CDocTemplate::InitialUpdateFrame只是简单调用了其基类的

void CFrameWnd::InitialUpdateFrame(CDocument* pDoc, BOOL bMakeVisible)

{

    // if the frame does not have an activeview, set to first pane

    CView* pView = NULL;

    if (GetActiveView()== NULL)

    {

        CWnd* pWnd = GetDescendantWindow(AFX_IDW_PANE_FIRST,TRUE);

        if (pWnd != NULL &&pWnd->IsKindOf(RUNTIME_CLASS(CView)))

        {

            pView = (CView*)pWnd;

            SetActiveView(pView, FALSE);

        }

    }

    if (bMakeVisible)

    {

        // send initial update to all views(and other controls) in the frame

        SendMessageToDescendants(WM_INITIALUPDATE,0, 0, TRUE, TRUE);

        // give view a chance to save thefocus (CFormView needs this)

        if (pView != NULL)

            pView->OnActivateFrame(WA_INACTIVE,this);

        // finally, activate the frame

        // (send the default show commandunless the main desktop window)

        int nCmdShow = -1;      // default

        CWinApp* pApp = AfxGetApp();

        if (pApp != NULL &&pApp->m_pMainWnd == this)//仅单窗口程序执行

        {

            nCmdShow = pApp->m_nCmdShow; //use the parameter from WinMain

            pApp->m_nCmdShow = -1; // set todefault after first time

        }

        ActivateFrame(nCmdShow);

        if (pView != NULL)

            pView->OnActivateView(TRUE,pView, pView);

    }

    // update frame counts and frame title(may already have been visible)

    if (pDoc != NULL)

        pDoc->UpdateFrameCounts();

    OnUpdateFrameTitle(TRUE);

}

    return pDocument;

首先调用GetActiveView()获取当前的活动视图,如果没有则调用GetDescendantWindow(AFX_IDW_PANE_FIRST,TRUE)获取子窗口,也就是视图窗口的指针,再调用SetActiveView将其设置为活动视图。bMakeVisible默认等于true,接下来再次调用SendMessageToDescendants初始化所有框架中的视图窗口。当框架中有视图窗口被激活或者取消激活时,CView::OnActivateFrame会被框架类调用。CView::OnActivateFrame本身是个空函数,但是可以在子类中重载它,以完成自己所需要的操作,这里我们并没有重载它。接下来将主框架指针m_pMainWnd设置为pFrame(单窗口程序),最后终于要正式激活框架窗口了,调用ActivateFrame函数激活框架窗口,

void CFrameWnd::ActivateFrame(int nCmdShow)

    // nCmdShow is the normal show modethis frame should be in

{

    // translate default nCmdShow (-1)

    if (nCmdShow == -1)

    {

        if (!IsWindowVisible())

            nCmdShow = SW_SHOWNORMAL;

        else if(IsIconic())

            nCmdShow = SW_RESTORE;

    }

    // bring to top before showing

    BringToTop(nCmdShow);

    if (nCmdShow != -1)//单文档程序才会执行

    {

        // show the window as specified

        ShowWindow(nCmdShow);

        // and finally, bring to top aftershowing

        BringToTop(nCmdShow);

    }

}

nCmdShow是WinMain的参数之一,指明了窗口将被如何显示,初始时为1,1代表的就是SW_SHOWNORMAL,(单文档程序和多文档程序在这里有所不同,单文档程序会执行标红的那两段,因为m_nCmdShow等于1,所以单文档程序中的nCmdShow被赋值为-1后又被重置为1。而多文档程序中nCmdShow等于-1,-1并不是nCmdShow可用的取值,至于为什么要弄得这么麻烦,主要是为了在多窗口程序中,能够适用于激活显示和从最小化恢复这两种情况,最终还是会将nCmdShow赋值为SW_SHOWNORMAL(=1)或SW_RESTORE(=9))然后会两次调用BringToTop函数(显示前和显示后)将框架窗口置于Z-Order的顶端(当然最终调用的是大家熟悉的:: BringWindowToTop,这里就不放源码了)。

接下来OnActivateView(TRUE,pView, pView)被调用,将焦点设置为被激活的视图。

void CView::OnActivateView(BOOLbActivate, CView* pActivateView, CView*)

{

    UNUSED(pActivateView);   // unused inrelease builds

    if (bActivate)

    {

        ASSERT(pActivateView== this);

        // take the focus if this frame/view/pane is now active

        if (IsTopParentActive())

            SetFocus();

    }

}

最后调用OnUpdateFrameTitle为框架更新标题栏。最后的最后返回pDocument,代表OpenDocumentFile函数调用过程的结束。至此,框架/文档/视图都已经被构建出来,并做好了显示前的一切准备工作。

 

终于我们回到了InitInstance函数中,剩下就是对窗口的显示和更新

m_pMainWnd->ShowWindow(SW_SHOW);

m_pMainWnd->UpdateWindow();//送出WM_PAINT消息

当然对于单文档程序来说,前面我们已经调用了一次ShowWindow显示了窗口,只是未更新。

而后返回true,结束InitInstance的调用过程。

2.1.4Run()的调用

结束了InitInstance的调用过程,我们终于回到了AfxWinMain函数中,最后就是调用Run()建立消息循环。来看看具体的定义,

int CWinApp::Run()

{

    if (m_pMainWnd == NULL &&AfxOleGetUserCtrl())

    {

        // Not launched /Embedding or /Automation, but has no mainwindow!

        TRACE(traceAppMsg,0, "Warning: m_pMainWnd is NULL inCWinApp::Run - quitting application.\n");

        AfxPostQuitMessage(0);

    }

    return CWinThread::Run();

}

可以看到里面又调用了CWinThread::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 isreceived.

    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));

    }

}

在接收到WM_QUIT消息之前,Run()会一直执行,进行消息的接收和发送。::PeekMessage在这里被用来查看线程消息队列中是否存在消息(虽然它也取出消息,但它并不等待消息,而是在消息不存在时返回0,消息存在时返回非0值,并且这里最后一个参数被设置为PM_NOREMOVE,也就是不从消息队列删除取到的消息,这也是为了等下::GetMessage再次取出该消息)如果空闲处理标志不为0而且在消息队列中没有消息的时候,会进行空闲时处理(当需要空闲处理时,我们应当重载OnIdle函数,并且在不需要更多空闲处理时间的时候,应当返回0,这时bIdle标志也就被置为FALSE)。如果从消息队列中接收到了消息,就会调用PumpMessage函数进行处理,

BOOL CWinThread::PumpMessage()

{

  return AfxInternalPumpMessage();

}

可以看到PumpMessage只是简单的调用了AfxInternalPumpMessage函数,

BOOL AFXAPI AfxInternalPumpMessage()

{

    _AFX_THREAD_STATE*pState = AfxGetThreadState();

 

    if (!::GetMessage(&(pState->m_msgCur),NULL, NULL, NULL))

    {

#ifdef _DEBUG

        TRACE(traceAppMsg,1, "CWinThread::PumpMessage - ReceivedWM_QUIT.\n");

            pState->m_nDisablePumpCount++;// application must die

#endif

        // Note: prevents calling message loop things in'ExitInstance'

        // will never be decremented

        return FALSE;

    }

#ifdef _DEBUG

  if (pState->m_nDisablePumpCount != 0)

    {

      TRACE(traceAppMsg, 0, "Error: CWinThread::PumpMessage called when notpermitted.\n");

      ASSERT(FALSE);

    }

#endif

#ifdef _DEBUG

    _AfxTraceMsg(_T("PumpMessage"),&(pState->m_msgCur));

#endif

  // process this message

    if (pState->m_msgCur.message != WM_KICKIDLE&& !AfxPreTranslateMessage(&(pState->m_msgCur)))

    {

        ::TranslateMessage(&(pState->m_msgCur));

        ::DispatchMessage(&(pState->m_msgCur));

    }

  return TRUE;

}

 

除去一些调试代码,可以看到这里面先调用了::GetMessage再次取出消息(要记住是在确定有消息后才会调用::GetMessage,所以并不会造成什么负担),并判断是否是WM_QUIT。如果是WM_QUIT会返回0,从而引发ExitInstance()的调用结束整个程序。如果是其他的消息,则会调用::TranslateMessage与::DispatchMessage,对消息进行翻译与发送,送往消息处理函数处。

最后我们来看看ExitInstance()做了些什么,

int CSDIApp::ExitInstance()

{

    //TODO: 处理可能已添加的附加资源

    AfxOleTerm(FALSE);

    return CWinAppEx::ExitInstance();

}

向导生成的程序帮我们重载了这个函数,我们可以在这里做一些额外的动作,或是处理已添加的附加资源。看以看到里面调用了CWinAppEx::ExitInstance(),

int CWinAppEx::ExitInstance()

{

    ControlBarCleanUp();

    return CWinApp::ExitInstance();

}

在清除了ControlBars之后又调用了CWinApp::ExitInstance(),

int CWinApp::ExitInstance()

{

    // if we remember that we're unregistering,

    // don't save our profile settings

    if (m_pCmdInfo == NULL ||

        (m_pCmdInfo->m_nShellCommand!= CCommandLineInfo::AppUnregister &&

         m_pCmdInfo->m_nShellCommand !=CCommandLineInfo::AppRegister))

    {

        if (!afxContextIsDLL)

            SaveStdProfileSettings();

    }

    // Cleanup DAO if necessary

    if (m_lpfnDaoTerm != NULL)

    {

        // If a DLL, YOU must call AfxDaoTerm prior to ExitInstance

        ASSERT(!afxContextIsDLL);

        (*m_lpfnDaoTerm)();

    }

    if (m_hLangResourceDLL != NULL)

    {

        ::FreeLibrary(m_hLangResourceDLL);

        m_hLangResourceDLL= NULL;

    }

    int nReturnValue=0;

    if(AfxGetCurrentMessage())

    {

        nReturnValue=static_cast<int>(AfxGetCurrentMessage()->wParam);

    }

    return nReturnValue; //returns the value from PostQuitMessage

}

可以看到这里主要是调用::FreeLibrary来释放加载的DLLs,并在最后调用AfxGetCurrentMessage(),获得WM_QUIT消息的wParam,也就是退出码,赋值给nReturnValue。再将其返回,赋值给nReturnCode。

 

2.1.5 AfxWinTerm()的调用

获得nReturnCode之后,调用了AfxWinTerm(),

void AFXAPI AfxWinTerm(void)

{  

    AfxUnregisterWndClasses();

    // cleanup OLE if required

    CWinThread*pThread = AfxGetApp();

    if (pThread != NULL &&pThread->m_lpfnOleTermOrFreeLib != NULL)

        (*pThread->m_lpfnOleTermOrFreeLib)(TRUE,FALSE);

    // cleanup thread local tooltip window

    AFX_MODULE_THREAD_STATE*pModuleThreadState = AfxGetModuleThreadState();

    if (pModuleThreadState->m_pToolTip != NULL)

    {

        if(pModuleThreadState->m_pToolTip->DestroyToolTipCtrl())

            pModuleThreadState->m_pToolTip= NULL;

    }

    _AFX_THREAD_STATE*pThreadState = AfxGetThreadState();

    if (!afxContextIsDLL)

    {

        // unhook windows hooks

        if (pThreadState->m_hHookOldMsgFilter != NULL)

        {

            ::UnhookWindowsHookEx(pThreadState->m_hHookOldMsgFilter);

            pThreadState->m_hHookOldMsgFilter= NULL;

        }

        if (pThreadState->m_hHookOldCbtFilter != NULL)

        {

            ::UnhookWindowsHookEx(pThreadState->m_hHookOldCbtFilter);

            pThreadState->m_hHookOldCbtFilter= NULL;

        }

    }

    // We used to suppress all exceptions here. But that's thewrong thing

    // to do. If this process crashes, we should allow Windowsto crash

    // the process and invoke watson.

}

可以看到这里依然是为程序的结束做一些清除工作,如卸载钩子等。值得注意的是最后由微软人员所作的注释,这个函数确实和老版本发生了一些变化,部分防御代码不再存在。原因正是他们所说的,在这里处理所有的意外是不对的做法,应当允许windows使进程崩溃并启用watson进行调试。

最后返回nReturnCode,结束WinMain函数的调用回到WinMainCRTStartup中。nReturnCode被传递给mainret,WinMainCRTStartup调用exit(mainret)或_c_exit()完成一些清理工作(如清理缓存,调用doexit完成全局对象的析构等),至于exit和_c_exit的区别可以参考MSDN(主要是会不会调用自动对象和临时对象的析构函数),最后返回mainret

给操作系统,由操作系统调用API函数::ExitProcess结束进程。

至此,我们的程序算是彻底结束了。

 


你可能感兴趣的:(mfc,mfc,SDI)