温故而知新。在.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个版本,对应如下:
它们都定义在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及其子类与文档模板的桥梁。侯捷老师的这幅图很好的说明了这一关系。
作为补充,将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进行处理:
命令行参数与执行的命令对应如下:
再看下面的程序就很清楚了,其实就是将传进来的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结束进程。
至此,我们的程序算是彻底结束了。