MFC(Microsoft Foundation Class,微软基础类库)是微软为了简化程序员的开发工作所开发的一套C++类的集合,是一套面向对象的函数库,以类的方式提供给用户使用。利用这些类,可以有效地帮助程序员完成Windows应用程序的开发。
即MFC是一个封装库, 该库封装了 Windows程序运行机制 一文中手动设计、注册、创建、显示刷新、进入消息循环等代码实现,使得开发人员开发Windows程序更加方便简洁。
这里使用VS2019新创建一个单文档MFC程序, 如下:
这里创建了一个基于 单个文档 类型, 样式是MFC Standard 的MFC程序, 创建后可以看到VS2019自动创建了以下5个类:
在MFC中,类的命名都以字母“C”开头, 对于一个单文档应用程序(即我们在创建项目时第二步应用程序类型中选择的“单个文档”),都有一个CMainFrame类、一个以**“C+项目名+App”为名字的类(例CSingleDocMFCApp)、一个以“C+项目名+Doc”为名字的类(例CSingleDocMFCDoc)和一个以“C+项目名+View”**为名字的类(例CSingleDocMFCView)。
这里可以直接编译运行生成一个窗口:
和之前的Windows程序相比,这里新建项目后并没有手动的进行一般Windows程序的开发步骤,即设计、注册、创建、显示刷新、消息循环等步骤开发,但运行后依旧创建显示了一个窗口,因为创建项目后自动生成了几个类, 因此这里就会认为是MFC这个库在创建项目后自动给我们完成了设计、注册、创建、显示刷新、消息循环等步骤。
但通过全局搜索几个自动创建的类发现,并没有Windows程序的入口函数,即WinMain。
我们之所以看不见这些,是因为微软在MFC的底层框架类中封装了这些每一个窗口应用程序都需要的步骤,目的主要是为了简化程序员的开发工作,但这也给我们在学习和掌握MFC程序时造成了很多不必要的困扰。
这里可以在VS安装目录下的MFC的源码文件appmodul.cpp中找到WinMain, 例我安装在D盘,右键用vs2019打开:
可以看到WinMain函数,重新编译运行,可以看到会运行到断点的WinMain函数中:
这里的问题是,如何通过MFC框架自动创建的代码进入到WinMain的?
其实每个MFC程序,都会有一个唯一的全局的应用程序对象,如下:
初始化该对象时,首先递归调用CSingleDocMFCApp的父类的构造函数,而CSingleDocMFCApp继承自CWinApp,因此这里我们断点到CWinApp构造函数中,发现确实如此:
m_pCurrentWinThread对象的类型是CWinThread,该类是CWinApp的父类。根据C++继承机制,可以确定这个this对象代表的是子类CTestApp的对象,即theApp。这会在后面的WinMain中引用。
当程序调用了CWinApp类的构造函数,并执行了CTestApp类的构造函数,且产生了theApp 对象之后,接下来就进入 WinMain 函数。
回到之前的WinMain函数中,发现里面只调用了一个AfxWinMain, 在MFC程序中, 以Afx开头的都是全局函数,该函数在winmain.cpp中实现:
断点①:调用pApp->InitApplilcation(), 这里的pApp指向的就是唯一的全局对象(在上面的CWinApp构造中断点部分指定),该函数完成MFC内部管理方面的工作。
断点②:调用pThread->InitInstance(), 这里的pThread同理也在上面CWinApp中的断点部分指定,因此会调用CSingleDocMFCApp::InitApplication(),在该函数中会构建MFC核心的Doc/View结构:
其中会CMainFrame的构造中完成窗口的设计、注册、创建、显示刷新,这里会在其创建之前调用CMainFrame::PreCreateWindow, 在该函数内又会调用其父类的版本CFrameWnd::PreCreateWindow, 会在该接口中完成窗口的注册。
因篇幅有限不再深入讲解,可参考上述步骤找到对应代码断点验证。
断点③:pThread->Run(), 即该应用程序(theApp)的消息循环。
至此,可以看到在没有手动添加代码的情况下,使用MFC向导生成的代码,便可代替此前 Windows程序运行机制 一文中的设计、注册、创建、显示刷新、进入消息循环等步骤。
最后,在总结下MFC框架创建窗口的整体过程:
■ 首先利用全局应用程序对象theApp 启动应用程序。正是产生了这个全局对象,基类CWinApp中的this指针才能指向这个对象。如果
没有这个全局对象,则程序在编译时不会出错,但在运行时就会出错。
■ 调用全局应用程序对象的构造函数,从而就会先调用其基类CWinApp的构造函数。后者完成应用程序的一些初始化工作,并将应用
程序对象的指针保存起来。
■ 进入WinMain函数。在AfxWinMain函数中可以获取子类(即唯一的全局对象theApp)的指针,利用此指针调用虚函数:InitInstance,根据多态性原理,实际上调用的是子类(CSingleDocMFCApp)的 InitInstance 函数。后者完成应用程序的一些初始化工作,包括窗口类的注册、创建,窗口的显示和更新。
■ 进入消息循环。虽然也设置了默认的窗口过程函数,但是,MFC 应用程序实际上是采用消息映射机制来处理各种消息的。当收到
WM_QUIT 消息时,退出消息循环,程序结束。
可以看到窗口的创建虽然都由MFC框架处理,但其实现原理依旧和Windows程序一样,即设计、注册、创建、显示刷新、进入消息循环这一步骤。
优点就是前面提到的简化程序员的开发工作,但可以明显感觉到MFC框架对Windows系统的依赖性,这就限制了其对跨平台的支持。