MFC 六大机制 (1) MFC程序的初始化

3 篇文章 1 订阅
本章将先介绍 MFC 的文档/视图结构,讲解实现该结构的数据结构,然后编写一个控制台应用程序来模拟 MFC 的初始化,理清 MFC 初始化的顺序,然后说明如何创建一个最基本的 MFC 应用程序。

MFC 最重要的结构 文档/视图结构

MFC 提供了构造Windows应用程序的框架,它不仅为应用程序建立标准的结构,生成一系列启动文件,还提供标准的图形用户界面如菜单、工具栏、状态条等供开发人员在程序中补充完善,开发人员只需要完成针对待定应用的代码即可。其中最重要的框架就是其于文档/视图结构的文档应用程序框架。
在文档/视图结构中,程序的数据储存在文档类中(作为文档类的数据成员),文档类是对数据的抽象表示。数据显示由视图负责,视图是程序窗口的客户区,框架窗口是客户区的框架,程序数据显示在窗口,用户通过视图与程序交互。
文档,视图和框架三者之间是相互关联,相互协调的,彼此都包含了指向对方的指针。文档、视图与框架的关系,如下图所示:




MFC文档/视图结构相关的类

一般情况下,采用文档/视图结构的应用程序至少应由以下对象组成:应用程序对象(CWinApp类派生的对象),框架窗口对象(CFrameWnd类派生的对象),文档对象(CDocument类派生的对象),视图对象(CView类派生的对象)。另外,还必须有一个负责管理文档和视图的文档模板类(CDocTemplate)。其中的主角是CDocument类(文档类)和 CView类(视图类),这就是文档/视图结构的由来。各类的作用分别介绍如下:

1.CWinApp

CwinApp (应用程序类) 提供了用户与 Windows 应用程序之间进行交流的界面。在实例化该类对象后,这个对象自动地把自身与 Widnows 系统建立联系,接收 Windows 传送的消息,并交给程序中相应的对象去处理,免去了程序员许多的工作,使得开发 Windows 序变得简单方便。
这个类中有一个重要的成员函数:InitInstance(),在 Windows 环境下,可以运行同一程序的多个实例,函数 InitInstance() 的作用是在生成的一个新的实例的时候,完成一些初始化的工作。另外还有一个函数InitApplication(),与前者的区别是它"每一个程序只调用一次",而 InitInstance() 是"每一个例程调用一次"。

2.CFrameWnd

CFrameWnd (框架窗口类) 是应用程序的框架窗口。所谓框架窗口是指包括菜单、工具栏、状态栏和窗口客户区的整个应用程序的主窗口,相当于简单应用程序框架中所提到的主窗口。在 MFC 程序中,一般并不需要经常对 CFrameWnd 类进行操作,更多的是对视窗类进行操作,达到对程序中的数据进行编辑的目的。

3.CView

CView (视图类) 派生于 Cwnd 类,用于管理文档/视图结构中的窗口客户区,这个窗口在文档/视图结构中称为视图。视图类专门用于对应用程序的数据进行显示,在视图类中有一个很重要的函数 OnDraw(),OnDraw()函数是用于进行应用程序数据显示的函数,一般在派生类中要重写这一个函数。在文档/视图结构中,重写的OnDraw()函数首先清空客户区窗口,然后再在窗口上绘制客户需要的内容,也就是说,OnDraw() 函数将集中管理所有的数据显示工作。

4.CDocument

CDocument (文档类) 虽然视图类负责应用程序数据的显示,但应用程序的数据一般不直接由视图类管理,而是作为文档类(CDocument)的数据成员,由文档类来进行集中管理,而且文档类将直接与磁盘相联系,把文档类中的数据存盘,或从磁盘中取出存盘的数据。视图类用 OnDraw() 函数展示数据,但应用程序的数据却存放在文档类中,视图类的函数 GetDocument() 的返回值就是指向文档类的指针,通过这个指针就可以访问 到文档类中的公有数据成员。文档类的数据要存盘或取盘要与磁盘进行数据传递,可以用 CFile 类结合CFileDialog 类实现。在文档/视图结构中,通过文档类中的序列化函数 Serialize() 就可以很简单的完成数据存取任务。文档/视图结构中,数据的传输过程如下图所示:



5.CDocTemplate

CDocTemplate(文档模板类)的作用是连接文档/视图结构中文档类,视图类和框架窗口类之间的关系,文档类,视图类和框架窗口类之间的关系是在文档模板类中建立起来的,同时文档模板类还加载了菜单以及与菜单资源使用的 ID 等资源。具体来说,在 CWinApp 派生类的 InitInstance() 函数中建立了文档模板类 CDocTemplate,并用文档模板类连接资源、框架窗口、文档和视图。

类层次结构


继承类图,左边为父类,右边为派生类。实线框中的类只有 CObject 在 "afx.h" 中,其余都在 "afxwin.h" 中,虚线框中全部是用户自己定义的派生类。

使用控制台程序模拟 MFC 初始化

注意!是使用控制台应用程序(Win32 Console Application)模拟而不是 MFC 程序!另外不用急着 copy 代码,我在文章底部附录一给出了代码下载链接和使用方法。

假设新建一个控制台应用程序叫"My",首先自然是编写上面五个重要类的派生类,其中 CDocTemplate  用于管理其他类的对象,可以被直接使用,这里就暂且不讨论了(它的派生类被声明在"afxwin.h"中)。于是创建四个头文件并声明其他类的派生类。

//My.h
class CMyApp : public CWinApp
{
public:
	CMyApp();
	virtual BOOL InitInstance();    //覆盖
};
//MyDoc.h

class CMyDoc : public CDocument
{
public :
	CMyDoc();
};
//MyFrame.h

class CMyFrame : public CFrameWnd
{
public:
	CMyFrame();
};
//MyView.h

class CMyView : public CView
{
public:
	CMyView();
};

这就是客户需要创建的所有头文件了,当你使用 IDE 创建一个 MFC 程序时,他会自动为你创建上述头文件。另外还需要注意下面这 3 个头文件,它们在 MFC 类库中是真实存在的,并且名称相同。"afx.h" 声明了 CObject 基类,"afxwin.h" 声明了 MFC 中使用的大部分类的基类,"stdafx.h" 是为了减少重复编译设置的,用于建立一个预编译的头文件 .PCH 和一个预定义的类型文件 STDAFX.OBJ。由于MFC体系结构非常大,包含许多头文件,如果每次都编译的话比较费时,因此我们把常用的 MFC头 文件都放在 stdafx.h 中,如 afxwin.h、afxext.h、afxdisp.h、afxcmn.h 等,然后让 stdafx.cpp 包含这个 stdafx.h 文件。这样,由于编译器可以识别哪些文件已经编译过,所以stdafx.cpp就只编译一次,并生成所谓的预编译头文件。

//stdfx.h

#include "afxwin.h"

//其他必要的头文件
//#include <......>
//#include <......>
//#include <......>

//链接必要的库
//#pragma comment(......)
//#pragma comment(......)
//#pragma comment(......)

//afxwin.h

#pragma once
#include "afx.h"

//CCmdTarget类声明
class CCmdTarget : public CObject
{
public:
	CCmdTarget();
};

//CDocument类声明
class CDocument : public CCmdTarget
{
public:
	CDocument();
};

//CWnd类声明
class CWnd : public CCmdTarget
{
public:
	CWnd();
	virtual BOOL Create();
	BOOL CreateEx();
	virtual BOOL PreCreateWindow();
};

//CFrameWnd类声明
class CFrameWnd : public CWnd
{
public:
	CFrameWnd();
};

//CView类声明
class CView : public CWnd
{
public:
	CView();
};

//CWinThread类声明
class CWinThread : public CCmdTarget
{
public:
	CWnd* m_pMainWnd;

	CWinThread();
	virtual BOOL InitInstance();
	virtual int Run();
};

//CWinApp类声明
class CWinApp : public CWinThread
{
public:
	CWinApp();
	virtual BOOL InitApplication();
	virtual BOOL InitInstance();    //覆盖
	virtual int Run();    //覆盖
};

说明几点,CWnd 类中有创建窗口的虚函数,Create,CreateEx(前者的扩展版本),PreCreateWindow。 因此实际编写其派生类时需要覆盖这些函数。CWinApp 中有重要的 InitApplication() 函数和继承自 CWinThread 的 InitInstance() 和 Run(),编写其派生类通常要覆盖它们。值得一提的是,CWinThread 有一个数据成员 CWnd* m_pMainWnd,它是指向框架窗对象的指针。

//afx.h

//演示需要,MFC实际上不包含<iostream>
#include <iostream>
using namespace std;

//实际上下面这些重定义写在<WinDef.h>中,MFC编程时通常会自动包含该头文件,为演示方便就写这了
typedef int BOOL;
#define TRUE 1;

//CObect类声明
class CObject
{
public:
	CObject();
};

"afx.h" 中声明了祖宗类 CObject。

好了,至此为止,已经完成了所有头文件的创建,接下来只需要编写它们对应的 .cpp 文件即可。处于演示需要,我们在所有类的构造函数中加上一句输出方便运行时查看。

首先是四个客户类的实现:

//MyDoc.cpp

#include "stdafx.h"
#include "MyDoc.h"

CMyDoc::CMyDoc()
{
	cout<<"CMyDoc Constructor."<<endl;
}
//MyFrame.cpp

#include "stdafx.h"
#include "MyFrame.h"

//CMyFrame类方法定义
CMyFrame::CMyFrame()
{
	cout<<"CMyFrame Constructor."<<endl;
}
//MyView.cpp

#include "stdafx.h"
#include "MyView.h"

//CMyView类方法定义
CMyView::CMyView()
{
	cout<<"CMyView Constructor."<<endl;
}
//My.cpp

#include "stdafx.h"
#include "My.h"
#include "MyFrame.h"
#include "MyDoc.h"
#include "MyView.h"

//CMyWinApp类方法定义
CMyApp::CMyApp()
{
	cout<<"CMyApp Constructor."<<endl;
}
BOOL CMyApp::InitInstance()    //覆盖
{
	cout<<"CMyApp::InitInstance()."<<endl;
	
	//下面的注释为 MFC 源码中的内容,使用RTTI实例化了CMyDoc、
	//CMyFrame、CMyView,并且使用 CDocTemplate 类来连接管理
	/*
	// 注册应用程序的文档模板。文档模板
	// 将用作文档、框架窗口和视图之间的连接
	CSingleDocTemplate* pDocTemplate;
	pDocTemplate = new CSingleDocTemplate(
		IDR_MAINFRAME,
		RUNTIME_CLASS(CMyDoc),
		RUNTIME_CLASS(CMyFrame),       // 主 SDI 框架窗口
		RUNTIME_CLASS(CMyView));
	if (!pDocTemplate)
		return FALSE;
	AddDocTemplate(pDocTemplate);
	*/

	this->m_pMainWnd = new CMyFrame();
	return TRUE;
}

//全局变量
CMyApp theApp;

int main()
{
	theApp.InitApplication();
	theApp.InitInstance();
	theApp.Run();
	return 0;
}

说明几点,InitInstance() 中的注释是 MFC 中的源码,我在这里贴出来是为了让大家理解 MFC 是在这个时候实例化了 CMyDoc、CMyFrame 和 CMyView,至于 RUNTIME_CLASS 是什么,下几章博客会详细介绍。

为什么 MFC 没有 WinMain()?

注意!最重要的东西来了!"My.cpp" 中有一个很关键的全局对象 CMyApp theApp,它是整个 MFC 初始化的关键。由于 C++ 中全局对象的构建将比程序进入点(DOS 环境为 main,Windows 为 WinMain)更早,因此 CMyApp 的构造函数最先被执行,完成一系列的初始化。注意到该演示程序里有 main() 函数,但是 MFC 程序中并没有(Window 程序主函数其实是 WinMain()),那么它去哪了呢?MFC 将 WinMain() 封装了起来,在 CMyApp theApp 实例化后会自动调用 WinMain() 函数并获得 theApp 对象的指针对其操作,让使用者看起来 theApp 才是程序的入口点。

然后是剩余的存在于MFC中 .cpp 文件:
//stdfx.cpp

#include "stdafx.h"
//afx.cpp

#include "afx.h"

//CObject类声明
CObject::CObject()
{
	cout<<"CObject Constructor."<<endl;
}
//afxwin.cpp

#include "afxwin.h"

//这里导入"CMyApp.h"是为了使用 theApp 全局变量以使用改造版的 AfxGetApp()
#include "My.h"
extern CMyApp theApp;

//CCmdTarget类方法定义
CCmdTarget::CCmdTarget()
{
	cout<<"CCmdTarget Constructor."<<endl;
}

//CDocument类方法定义
CDocument::CDocument()
{
	cout<<"CDocument Constructor."<<endl;
}

//CWnd类方法定义
CWnd::CWnd()
{
	cout<<"CWnd Constructor."<<endl;
}
BOOL CWnd::Create()
{
	cout<<"CWnd::Create()."<<endl;
	return TRUE;
}
BOOL CWnd::CreateEx()
{
	cout<<"CWnd::CreateEx()."<<endl;
	return TRUE;
}
BOOL CWnd::PreCreateWindow()
{
	cout<<"CWnd::PreCreateWindow()."<<endl;
	return TRUE;
}

//CFrameWnd类方法定义
CFrameWnd::CFrameWnd()
{
	cout<<"CFrameWnd Constructor."<<endl;
}

//CView类方法定义
CView::CView()
{
	cout<<"CView Constructor."<<endl;
}

//CWinThread类方法定义
CWinThread::CWinThread()
{
	cout<<"CWinThread Constructor."<<endl;
}
BOOL CWinThread::InitInstance()
{
	cout<<"CWinThread::InitInstance()."<<endl;
	return TRUE;
}
int CWinThread::Run()
{
	cout<<"CWinThread::Run()."<<endl;
	return 1;
}

//CWinApp类方法定义
CWinApp::CWinApp()
{
	cout<<"CWinApp Constructor."<<endl;
}
BOOL CWinApp::InitInstance()
{
	cout<<"CWinApp::InitInstance()."<<endl;
	return TRUE;
}
BOOL CWinApp::InitApplication()
{
	cout<<"CWinApp::InitApplication()."<<endl;
	return TRUE;
}
int CWinApp::Run()
{
	cout<<"CWinApp::Run()."<<endl;
	return 1;
}

值得注意的是 InitInstance() 虚函数是在 CWinThread 中被声明,InitApplication() 则是在 CWinApp中被声明。

呼!大功告成!运行结果如下,可以从结果看一下 MFC 的初始化流程:
运行结果:
CObject Constructor.
CCmdTarget Constructor.
CWinThread Constructor.
CWinApp Constructor.
CMyApp Constructor.
CWinApp::InitApplication().
CMyApp::InitInstance().
CObject Constructor.
CCmdTarget Constructor.
CWnd Constructor.
CFrameWnd Constructor.
CMyFrame Constructor.
CWinApp::Run().

简单总结一下 MFC 的初始化过程:

最先初始化全局对象 theApp,在 theApp 的 InitInstance() 函数中完成初始化,包括框架窗口类、视图类、文档类(和文档模板类),然后通过消息机制让这些类的对象进行通讯,需要注意的是这些类的继承层次。

创建一个 MFC 工程

接下来就要创建一个真正的 MFC 程序并与上面的模拟 MFC 进行对比了,创建方法在文章底部附录二。那么 如何查看 "afxwin.h" 和 "afx.h" 呢?举个例子,你可以双击"My.cpp"将鼠标移到 CMyApp 上右键->转到定义即可方便地跳转到相应位置了。相应现在应该能对这一堆文件有一个大致的定位了。



附录一:使用已有代码创建 MyMFC


文件->新建->Visual C++ ->Win32->Win32控制台应用程序 修改名称为 MyMFC


单击下一步,选择[空项目],单击确定。
打开你的工程目录(如果不知道在哪,单击文件->最近的文件即可看到),进入MyMFC,再进入MyMFC,将所有代码拷贝至当前位置。(代码在文章底部有下载链接)


在[解决方案资源管理器]界面中右键头文件->添加->现有项,找到代码拷贝的位置,将所有头文件添加进工程。(按住Ctrl键可多选) 源文件也同样操作。




双击 My.cpp 编译(Ctrl+F7),运行(Ctrl+F5)。

附录二:创建MFC工程

文件->新建->Visual C++ ->MFC->MFC应用程序,将名称改为:My ->确定


选择 ·单个文档 ·在静态库中使用MFC


单击 [下一步] 直到 [生成的类] 这一界面,单击 CMainFrame,将类名/文件名修改为 MyFrame(为了形式统一)


单击完成

注意!需先在[解决方案资源管理器]中打开 stdafx.cpp 进行编译(Ctrl+F7),再打开 My.cpp 进行编译,才可运行(Ctrl+F5),否则会报错。(因为需要先导入 stdafx.h 中的内容) 运行结果如下所示(已经关闭了无关窗口)。


注意!如果程序报错:[无法打开包括文件:“MainFrm.h”: No such file or directory] 是因为VS修改文件名出了点小问题,很简单,双击该错误,将[#include "MainFrm.h"]修改为[#include "MyFrame.h"]重新编译即可。
  • 11
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值