偶然翻到以前学MFC时做的笔记,又回想起那段激情燃烧的岁月。当初为了学MFC还是下了一番功夫的,正好最近写文章有些拖延,就把这些笔记整理了一下发上来炒炒冷饭。如果能帮到一两位同好也算是皆大欢喜的事情了。
编译与运行
这是一个最简单的MFC应用程序,我们通过它来学习一些MFC的基础知识。首先新建一个Win32 Application,代码如下:
源代码:
#include <afxwin.h>
class CMyWnd : public CFrameWnd
{
public:
CMyWnd()
{
MessageBox(TEXT("In CMyWnd::CMyWnd"), TEXT("In CMyWnd::CMyWnd"), MB_OK);
Create(NULL, TEXT("My MFC Window"));
}
~CMyWnd()
{
MessageBox(TEXT("In CMyWnd::~CMyWnd"), TEXT("In CMyWnd::~CMyWnd"), MB_OK);
}
};
class CMyApp : public CWinApp
{
public:
CMyApp()
{
MessageBox(NULL, TEXT("In CMyApp::CMyApp"), TEXT("In CMyApp::CMyApp"), MB_OK);
}
BOOL InitInstance();
BOOL ExitInstance();
~CMyApp()
{
MessageBox(NULL, TEXT("In CMyApp::~CMyApp"), TEXT("In CMyApp::~CMyApp"), MB_OK);
}
};
BOOL CMyApp::InitInstance()
{
MessageBox(NULL, TEXT("InitInstance"), TEXT("InitInstance"), MB_OK);
CMyWnd *pCMyWnd = new CMyWnd();
m_pMainWnd = pCMyWnd;
pCMyWnd->ShowWindow(SW_SHOWNORMAL);
return TRUE;
}
BOOL CMyApp::ExitInstance()
{
MessageBox(NULL, TEXT("ExitInstance"), TEXT("ExitInstance"), MB_OK);
return TRUE;
}
CMyApp theApp;
解决编译、链接过程中的错误
创建代码后,编译链接时提示找不到符号,为此,修改工程项目属性,引入MFC的静态库:
编译时,会报MFC静态库中的符号和libcmt中的冲突(其实就是libcmt中的符号重复了),将其忽略即可:
这样,就能正常编译了。
程序的组织结构:
全局观:
- theApp全局对象创建,CMyWinApp::CMyWinApp及其父类、爷爷类、…、祖宗类的构造函数被调用
- 进入WinMain函数,通过CWinApp对象的指针(指向的是全局对象theApp的地址)调用InitInstance,CMyWinApp::InitInstance被调用
- 在CMyWinApp::InitInstance中创建CMyWnd对象,触发CMyWnd::CMyWnd调用,在其中完成CreateWindow
- CMyWnd创建完后,在CMyWinApp::InitInstance通过指针调用其ShowWindow函数,窗口出现
- 窗口关闭后,CMyWnd析构
- 程序结束时,CMyWinApp::ExitInstance被调用,CMyWinApp析构
theApp对象与消失的WinMain
- theApp对应的类是控制整个应用程序的,所以称为CWinApp类,是不可或缺的一个类。而且要运行程序,要将该类实例化。
- 全局对象的实例化先于WinMain函数,故CMyApp的构造函数以及其父类的构造函数都会被调用(最先被调用的其实是CMyApp::CMyApp(),但在其一开始的位置调用了CWinApp::CWinApp()所以效果上是父类的构造函数先被调用)
- 其构造函数被调用后,程序进入WinMain函数,在其中(WinMain->AfxWinMain)调用InitInstance。WinMain在MFC框架的源程序里:
作为验证,我们来探望一下位于appmodul.cpp中的WinMain:
并且找到AfxWinMain中调用InitInstance中的地方:
其中,pThread是什么鬼呢?
答案:pThread是CWinThread类的指针,而CWinThread类是CMyWinApp类的爷爷,族谱在此:
因此,名正言顺地就把CMyWinApp::InitInstance函数给调用了
CMyApp::InitInstance与CMyWnd的纠葛
创建CMyWnd对象
CMyWinApp::InitInstance是我们自己写的,其中有一句
CMyWnd *pCMyWnd = new CMyWnd();
这样就动态创建了CMyWnd的对象,CMyWnd::CMyWnd被调用
创建窗口
而在其中,主要调用了Create方法在内存中创建窗口,而在其中又会调用CreateEx函数,最终调用CreateWindow函数。
而CreateWindow后,还需要ShowWindow,这一步在CMyWinApp::InitInstance中完成。至此,窗口创建成功。
窗口关闭后
窗口关闭后,CMyWnd的爷爷CWnd响应了WM_DESTROY消息。响应分成两步:
1. 调用PostQuitMessage退出消息循环:
2. 调用PostNcDestroy函数,该函数被CMyWnd的爷爷CFrameWnd重载了,在其中调用delete自杀了
这将导致CMyWnd对象被析构:
而PostQuitMessage发出的WM_QUIT消息进入消息队列后,导致位于CWinThread中的消息循环结束,调用到它孙子的CMyWnd::ExitInstance:
CMyApp::ExitInstance()被调用
CMyApp::ExitInstance()被调用后,执行完后续代码后WinMain返回,最后CMyApp析构。
其他细节
int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
_In_ LPTSTR lpCmdLine, int nCmdShow)
{
ASSERT(hPrevInstance == NULL);
int nReturnCode = -1;
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
// AFX internal initialization
if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)) goto InitFailure;
// App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication()) goto InitFailure;
// Perform specific initializations
if (!pThread->InitInstance())
{
if (pThread->m_pMainWnd != NULL)
{
TRACE(traceAppMsg, 0, "Warning: Destroying non-NULL m_pMainWnd\n");
pThread->m_pMainWnd->DestroyWindow();
}
nReturnCode = pThread->ExitInstance();
goto InitFailure;
}
nReturnCode = pThread->Run();
InitFailure:
AfxWinTerm();
return nReturnCode;
}
1.InitInstance与InitApplication
这两个函数都是在CWinApp中声明的虚函数,前者负责每个实例都要初始化一次的动作,后者负责一个应用中只需要初始化一次的操作。由于在我们的CMyApp中重载了InitInstance,所以最终调用的是CWinApp::InitApplication和CMyApp::InitInstance
2.CMyWnd构造函数中的Create
由于CMyWnd没有重载Create,故调到的是CFrameWnd::Create,其中会完成菜单资源和程序标题初始化(如果有的话),并调用CreateEx;而CFrameWnd没有重载CreateEx,故调到的是CWnd::CreateEx,该函数中调用了PreCreateWindow,这是一个在CWnd中定义的虚函数,可被子类覆盖,所以调用的是CFrameWnd::PreCreateWindow。之后,会调用AfxHookWindowCreate,这个函数会完成一项很重要的工作,留在第三节中讨论,完成后,调用CreateWindowEx创建窗口。
本文通过分析一个最简单的MFC应用程序,探讨MFC的编译运行过程、程序组织结构,包括全局对象theApp、WinMain的替代、CMyApp::InitInstance与CMyWnd的关系,以及窗口创建和关闭的详细步骤。在解决编译链接错误后,深入理解MFC框架下的窗口生命周期和对象构造析构顺序。
1332





