文档/视图体系结构
MFC应用程序框架结构的基石是文档/视图体系结构,它定义了一种程序结构,这种结构依靠文档对象保存应用程序的数据,并依靠视图对象控制视图中显示的数据。
MFC在类CDocument
和CView
中为文档和视图提供了基础结构。CWinApp
、CFrameWnd
和其他类与CDocument
和CView
合作,把所有的片段连在了一起。
文档/视图应用程序从应用程序框架结构中得到了最大的好处。几乎任何依赖某类文档的程序都能用文档/视图生成。文档/视图体系结构极大地简化了打印和打印预览、向磁盘中存储文档以及读取文档的过程。
别被术语“文档”误导,认为文档/视图体系结构对编写字处理器和电子表格程序有用。文档仅仅是程序数据的抽象表示。文档既可能是保存计算机象棋游戏中棋盘位置的字节数组,也可能是一个电子表格。
MFC程序的分类
- MFC控制台程序:可以调用MFC的控制台程序
- MFC库程序
- MFC静态库
- MFC动态库
- MFC扩展库:扩展库只能被MFC应用程序调用
- MFC窗口程序
- 单文档视图架构程序(SDI)
- 多文档视图架构程序(MDI)
- 对话框应用程序
MFC应用程序的构成
在Visual Studio中,如果已经以Debug方式编译链接过程序,则会在解决方案文件夹下和工程子文件夹下各有一个名为“Debug”的文件夹,而如果是Release方式编译则会有名为“Release”的文件夹。这两种编译方式将产生两种不同版本的可执行文件:
- Debug版本
可执行文件中包含了用于调试的信息和代码。 - Release版本
没有调试信息,不能进行调试,但可执行文件较小。
创建Hello World工程,工程中文件结构:
-
解决方案相关文件
包括解决方案文件夹下的.sdf
文件、.sln
文件、.suo
文件和ipch
文件夹。.sdf
文件和ipch
目录一般与智能提示、错误提示、代码恢复和团队本地仓库有关。.sln
文件和.suo
文件为MFC自动生成的解决方案文件,它包含当前解决方案中的工程信息,存储解决方案的设置。
-
工程相关文件
包括工程文件夹下的.vcxproj
文件和.vcxproj.filters
文件。.vcxproj
文件是MFC生成的工程文件,它包含当前工程的设置和工程所包含的文件等信息。.vcxproj.filters
文件存放工程的虚拟目录信息,也就是在解决方案浏览器中的目录结构信息。
-
资源文件
一般使用MFC生成窗口程序都会有对话框、图标、菜单等资源,应用程序向导会生成资源相关文件:res
目录、HelloWorld.rc
文件和Resource.h
文件。res
目录:工程文件夹下的res目录中含有应用程序默认图标、工具栏使用图标等图标文件。HelloWorld.rc
:包含默认菜单定义、字符串表,制定了默认的About对话框和应用程序默认图标文件等。resource.h
:含有各种资源的ID定义。
-
预编译头文件
几乎所有MFC程序的文件都要包含afxwin.h
等文件(外部依赖项),如果每次都编译一次则会大大减慢编译速度。所以把常用的MFC头文件都放到了stdafx.h
中,然后由stdafx.cpp
包含stdafx.h
文件,编译器对stdafx.cpp
只编译一次,并生成编译后的预编译头HelloWorld.pch
(Precomplied Header Flie),大大提高了编译效率。
"stdafx.h"
前的代码都是预编译的,它跳过#include "stdafx.h"
指令,使用projectname.pch
编译这条指令之后的所有代码。因此,所有MFC程序cpp实现文件的第一条语句都是
#include "stdafx.h"
-
编译链接生成的文件
工程文件夹下的Debug和Release子文件夹中包含了编译链接时产生的中间文件。
解决方案文件夹下的Debug和Release子文件夹中主要包含有应用程序的可执行文件。 -
应用程序头文件和源文件
应用程序向导(MFC Application Wizard)会根据应用程序的类型(单文档、多文档或基于对话框的程序)自动生成一些头文件和源文件,这些文件是工程的主体部分,用于实现主框架、文档、视图等。
文件 | 用途 |
---|---|
stdafx.h | 标准Afx头文件 |
stdafx.cpp | #include "stdafx.h" 用来产生预编译的类型信息 |
resource.h | 定义各种资源ID |
HelloWorld.h | 定义了从CWinApp 类派生的应用程序对象HelloWorldApp |
HelloWorld.cpp | 定义了HelloWorldApp 的实现,并定义HelloWorldApp 类型的全局变量theApp |
MainFrm.h | 定义了从CFrameWnd 派生的框架窗口对象MainFrame |
MainFrm.cpp | MainFrame 类的实现 |
HelloWorldDoc.h | 定义了从CDocument 派生的文档对象 HelloWorldDoc |
HelloWorldDoc.cpp | HelloWorldDoc 类的实现 |
HelloWorldView.h | 定义了从CView 派生的视图对象 HelloWorldView |
HelloWorldView.cpp | HelloWorldView 类的实现 |
- HelloWorldApp的实现用到了所有的用户定义对象,包含了其他类的定义;
- HelloWorldView的实现用到了HelloWorldDoc;
- 其他对象的实现只涉及自己的定义。
MFC应用程序框架主要类之间的关系
HelloWorldApp
类处理消息,将收到的消息分发给相应的对象。MainFrame
类是视图类HelloWorldView
的父窗口,HelloWorldView
就显示在MainFrame
的客户区中。- 视图类
HelloWorldView
用来显示文档类HelloWorldDoc
中的数据,并根据对视图类的操作修改文档类的数据。一个视图类只能跟一个文档类相联系,而一个文档类可以跟多个视图类相联系。
参与架构的类:
//应用程序类:负责程序流程
class HelloWorldApp : public CWinApp
//单文档SDI主框架窗口类
class MainFrame : public CFrameWnd
//视图窗口类:显示数据
class HelloWorldView : public CView
//文档类:数据管理
class HelloWorldDoc : public CDocument
//如果是多文档MDI工程
//多文档主框架窗口类
class MainFrame : public CMDIFrameWnd
//多文档子框架窗口类
class ChildFrame : public CMDIChildWnd
SDK应用程序运行流程
只使用Windows API,不使用MFC创建一个HelloWorld工程,即SDK应用程序。
SDK应用程序的入口函数为WinMain
函数。
#include <windows.h>
LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
const static TCHAR appName[] = TEXT("Hello world");
WNDCLASSEX myWin;
myWin.cbSize = sizeof(myWin);
myWin.style = CS_HREDRAW | CS_VREDRAW;
myWin.lpfnWndProc = myWndProc;
myWin.cbClsExtra = 0;
myWin.cbWndExtra = 0;
myWin.hInstance = hInstance;
myWin.hIcon = 0;
myWin.hIconSm = 0;
myWin.hCursor = 0;
myWin.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
myWin.lpszMenuName = 0;
myWin.lpszClassName = appName;
//Register
if (!RegisterClassEx(&myWin)) return 0;
const HWND hWindow = CreateWindow(
appName,
appName,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
0,
0,
hInstance,
0);
ShowWindow(hWindow,iCmdShow);
UpdateWindow(hWindow);
{
MSG msg;
while(GetMessage(&msg,0,0,0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return (int)msg.wParam;
}
}
LRESULT CALLBACK myWndProc(HWND hWindow, UINT msg, WPARAM wParam, LPARAM lParam)
{
if (msg==WM_PAINT)
{
PAINTSTRUCT ps;
const HDC hDC = BeginPaint(hWindow,&ps);
RECT rect;
GetClientRect(hWindow,&rect);
DrawText(hDC,TEXT("HELLO WORLD"),-1,&rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hWindow,&ps);
return 0;
}
else if (msg==WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
return DefWindowProc(hWindow,msg,wParam,lParam);
}
程序运行流程
- 进入
WinMain
函数 - 初始化
WNDCLASSEX
,调用RegisterClassEx
函数注册窗口类 - 调用
ShowWindow
和UpdateWindow
函数显示并更新窗口 - 进入消息循环
关于消息循环
Windows应用程序是消息驱动的,系统或用户让应用程序进行某项操作或完成某个任务时会发送消息,进入程序的消息队列,然后消息循环会将消息队列中的消息取出,交予相应的窗口过程处理。
此程序的窗口过程函数就是myWndProc函数,窗口过程函数处理完消息就完成了某项操作或任务。本例是要显示“HELLO WORLD”字符串,UpdateWindow函数会发送WM_PAINT消息,但是此消息不经过消息队列而是直接送到窗口过程处理,在窗口过程函数中最终绘制了“HELLO WORLD”字符串。
MFC应用程序运行流程
下面是MFC应用程序的运行流程,通过MFC库中代码进行分析:
MFC应用程序的核心就是基于CWinApp类的应用程序对象,CWinApp提供了消息循环来检索消息并将消息调度给应用程序的窗口。
一个MFC应用程序可以有且仅有一个应用程序对象,对象必须声明为在全局范围内有效(也就是全局对象),以便它在程序开始时即在内存中被实例化。
1.定义全局对象theApp,构造函数进入应用程序入口
extern HelloWorldApp theApp;
2.实例化,并创建显示一个窗口
成员函数:InitInstance()
功能:初始化应用程序实例和窗口实例,
虚函数CWinApp::InitInstance
必须在派生类中重写。在InitInstance
函数中,编写初始化代码
BOOL HelloWorldApp::InitInstance()
{
//......略
CWinApp::InitInstance();
// 注册应用程序的文档模板。 文档模板将用作文档、框架窗口和视图之间的连接
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CHelloWorldDoc),
RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口
RUNTIME_CLASS(CHelloWorldView));
if (!pDocTemplate)
return FALSE;
AddDocTemplate(pDocTemplate);
// 分析标准 shell 命令、DDE、打开文件操作的命令行
CCommandLineInfo cmdInfo;
ParseCommandLine(cmdInfo);
// 调度在命令行中指定的命令。 如果用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。
if (!ProcessShellCommand(cmdInfo))
return FALSE;
// 唯一的一个窗口已初始化,因此显示它并对其进行更新
m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();
return TRUE;
}
InitInstance
中的ProcessShellCommand
函数又调用了MainFrame
的LoadFrame
函数注册并创建了窗口,执行完ProcessShellCommand
函数以后,调用了m_pMainWnd
的ShowWindow
和UpdateWindow
函数显示并更新框架窗口。这些与上面的SDK程序十分类似
class MainFrame : public CFrameWnd
MainFrame
为应用程序提供一个窗口,同时实现消息处理功能。OnCreate()
创建窗体,将之赋于CFrameWnd对象上。
3.进入消息循环
了,上面的AfxWinMain
函数中调用了pThread
的Run
函数(位于THRDCORE.cpp
中),在Run中包含了消息循环。Run函数的代码如下:
int CWinThread::Run()
{
//.............略
// phase2: pump messages while available
do
{
// pump message, but quit on WM_QUIT
if (!PumpMessage())
return ExitInstance();
// reset "no idle" state after pumping "normal" message
if (IsIdleMessage(&m_msgCur))
{
bIdle = TRUE;
lIdleCount = 0;
}
} while (::PeekMessage(&m_msgCur, NULL, NULL, NULL, PM_NOREMOVE));
//..............略
}
BOOL CWinThread::PumpMessage()
{
return AfxInternalPumpMessage();
}
BOOL AFXAPI AfxInternalPumpMessage()
{
_AFX_THREAD_STATE *pState = AfxGetThreadState();
if (!::GetMessage(&(pState->m_msgCur), NULL, NULL, NULL))
{
//.............略
}
//...............略
if (pState->m_msgCur.message != WM_KICKIDLE && !AfxPreTranslateMessage(&(pState->m_msgCur)))
{
::TranslateMessage(&(pState->m_msgCur));
::DispatchMessage(&(pState->m_msgCur));
}
return TRUE;
}
可以看到PumpMessage
中通过调用GetMessage
、TranslateMessage
、DispatchMessage
等建立了消息循环并投递消息。
窗口过程函数AfxWinProc
形式如下:
LRESULT CALLBACK AfxWndProc(HWND hWnd,UINT nMsg,WPARAM wParam, LPARAM lParam)
{
……
CWnd*pWnd=CWnd::FromHandlePermanent(hWnd);
Return AfxCallWndProc(pWnd,hWnd,nMsg,wParam,lParam);
}
SDK应用程序和MFC应用程序对比:
运行流程是类似的,都是先进行初始化过程,再注册并创建窗口,然后显示、更新窗口,最后进入消息循环,消息都由窗口过程函数处理。