MFC源码实战分析(一)——从一个最简MFC程序说开去

本文通过分析一个最简单的MFC应用程序,探讨MFC的编译运行过程、程序组织结构,包括全局对象theApp、WinMain的替代、CMyApp::InitInstance与CMyWnd的关系,以及窗口创建和关闭的详细步骤。在解决编译链接错误后,深入理解MFC框架下的窗口生命周期和对象构造析构顺序。

偶然翻到以前学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中的符号重复了),将其忽略即可:
这里写图片描述
这样,就能正常编译了。

程序的组织结构:

全局观:

  1. theApp全局对象创建,CMyWinApp::CMyWinApp及其父类、爷爷类、…、祖宗类的构造函数被调用
  2. 进入WinMain函数,通过CWinApp对象的指针(指向的是全局对象theApp的地址)调用InitInstance,CMyWinApp::InitInstance被调用
  3. 在CMyWinApp::InitInstance中创建CMyWnd对象,触发CMyWnd::CMyWnd调用,在其中完成CreateWindow
  4. CMyWnd创建完后,在CMyWinApp::InitInstance通过指针调用其ShowWindow函数,窗口出现
  5. 窗口关闭后,CMyWnd析构
  6. 程序结束时,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创建窗口。

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值