引言:
侯捷老师在他那本著名的"深入浅出MFC"(第二版)的第六章中对比着传统的Win32API编程,详细讲解了MFC应用程序“生死因果”,而且侯捷老师还在"深入浅出MFC"(第二版)一书的“无责任书评”中称应用程序和MFC Framework的因果关系,是学习MFC程序设计的关键,并把它作为学习MFC程序设计的"第一个台阶".
作为已是“过来人”的我非常赞同侯捷老师的观点,特写下此篇文章以供大家参考,本文章特别对MFC程序设计的初学者大有裨益。
正文:
初学MFC程序设计的人(甚至包括已经很精通Win32API编程的大虾们)都会感到很疑惑,对MFC应用程序的运行流程不能马上领悟,多数人都会提出类似"WinMain函数跑到哪里去了?","窗口函数(WinProc),消息循环好像一下子都消失了?"等问题。下面就让我们看一个MFC SDI应用程序的运行流程并挖掘一下MFC库的源代码,来尽力争取弄清MFC应用程序“生死因果”的内幕。
////////////////////////////////////////////
/* 1. Windows 帮忙 */
/* 程序诞生! */
//////////////////////////////////////////
Windows 操作系统为应用程序创建进程核心对象,并为该应用程序分配4GB的进程地址空间,系统加载器
将应用程序可执行文件映像以及一些必要的代码(包括数据和一些应用程序使用的dlls)加载到应用程序的进程地址空间中。
/////////////////////////////////////////////
/* 2.启动函数是什么? */
/////////////////////////////////////////////
Windows 操作系统在初始化该应用程序进程的同时,将自动为该应用程序创建一个主线程,该主线程与
C/C++运行时库的启动函数一道开始运行。很多初学者并不知道C/C++运行时库的启动函数是何方神圣,这里我
简单介绍一下:当你的应用程序编译后开始链接时,系统的链接器会根据你的应用程序的设置为你的应用程序
选择一个C/C++运行时库的启动函数(注释:这些函数声明在../Visual Studio.NET/vc7/crt/src/crt0.c中)
一般的ANSI版本的GUI的应用程序的C/C++运行时库的启动函数为:
int WinMainCRTStartup(void);
其它版本的C/C++运行时库的启动函数如下:
ANSI版本的CUI的应用程序: int mainCRTStartup(void);
Unicode版本的CUI的应用程序: int wmainCRTStartup(void);
Unicode版本的GUI的应用程序: int wWinMainCRTStartup(void);
C/C++运行时库的启动函数的主要功能为初始化C/C++运行时库和为所有全局和静态的C++类对象调用构造函数。
/////////////////////////////////////////////////
/* 3.侯捷老师所说的"引爆器" */
/////////////////////////////////////////////////
前面所说的C/C++运行时库的启动函数的主要功能之一是为所有全局和静态的C++类对象调用构造函数。侯捷老师所说的"引爆器"---CMyWinApp theApp这个Application Object就是由启动函数调用其构造函数构造出来的。CWinApp的构造函数到底作了什么?看看源代码吧,源代码最能说明问题了。
注释:CWinApp的构造函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appcore.cpp
CWinApp::CWinApp(LPCTSTR lpszAppName)
{
if (lpszAppName != NULL)
m_pszAppName = _tcsdup(lpszAppName);
else
m_pszAppName = NULL;
// initialize CWinThread state
AFX_MODULE_STATE* pModuleState = _AFX_CMDTARGET_GETSTATE();
AFX_MODULE_THREAD_STATE* pThreadState = pModuleState->m_thread;
ASSERT(AfxGetThread() == NULL);
pThreadState->m_pCurrentWinThread = this;
ASSERT(AfxGetThread() == this);
m_hThread = ::GetCurrentThread();
m_nThreadID = ::GetCurrentThreadId();
// initialize CWinApp state
ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);
// in non-running state until WinMain
m_hInstance = NULL;
m_hLangResourceDLL = NULL;
m_pszHelpFilePath = NULL;
m_pszProfileName = NULL;
m_pszRegistryKey = NULL;
m_pszExeName = NULL;
m_pRecentFileList = NULL;
m_pDocManager = NULL;
m_atomApp = m_atomSystemTopic = NULL;
m_lpCmdLine = NULL;
m_pCmdInfo = NULL;
// initialize wait cursor state
...//
// initialize current printer state
...//
// initialize DAO state
m_lpfnDaoTerm = NULL; // will be set if AfxDaoInit called
// other initialization
...//
}
从源代码中可以看出CWinApp的构造函数主要收集了一些关于应用程序主线程的信息及初始化一些相关应用程序的信息。值得注意的是CWinApp类的一些主要的数据成员如:m_hInstance,m_lpCmdLine,m_pCmdInfo及m_atomApp等都初始化为NULL,这些成员在后面将被重新赋值。
//////////////////////////////////////////////
/* 4. WinMain函数登场了 */
//////////////////////////////////////////////
C/C++运行时库的启动函数int WinMainCRTStartup(void);所调用的WinMain函数---同时也是主线程的入口函数为:
int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,LPTSTR lpCmdLine
,int nCmdShow);
注释1:该函数定义在../Visual Studio.NET/vc7/atlmfc/src/mfc/appmodul.cpp中
注释2:_t 是为了照顾Unicode版本而定义的宏。
讲到这个时候你也许会稍稍展开你那紧皱的眉头,不过也许你还会问:"MFC中的WinMain函数到底作了什么?" 其实很简单,看看源代码就知道了。
extern "C" int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
LPTSTR lpCmdLine, int nCmdShow)
{
// call shared/exported WinMain
return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);
}
这一下清楚了,MFC中的WinMain函数其实什么也没做,只是调用了一个函数AfxWinMain。
/////////////////////////////////////////////////
/* 5.MFC程序的入口点函数 */
//////////////////////////////////////////////////
MFC作了一个"乾坤大挪移",将WinMain函数的全部责任转移交给了MFC程序的入口点函数---AfxWinMain。
我自已架设了博客,文章已转到个人博客,欢迎交流!
MFC技术内幕系列之(一)---MFC应用程序“生死因果”内幕
http://www.jeanva.cn/post/53.html