MFC简介
微软提供的一个类库(Class Libraries),封装了Windows的API,并且包含一个应用程序框架。
1、MFC是对前面窗口编程所用到的API进行封装,在Windows C基础上引入了C++面向对象的思想,简单而言就是把API函数和处理数据捆绑一起,做成了类——当然封装、继承、多态的机制也应用起来;
2、MFC不仅仅是类库(类的一个集合),而且还是一个应用程序框架——已经搭建好了程序的“骨架”(毛坯房),程序员只需要”修修改改“,实现实际功能即可。
理解:
1、Windows C 当然需要掌握,MFC把API进行了一层简单的包装,函数名称、功能、调用的方法大体相同——所以学MFC,必先了解SDK编程;
2、框架的意义在于,不是让程序员自主地调用MFC里的类,而是以框架为基础,被动地接受这种开发的模式——降低”编码“的难度?包括了两(三)种应用程序开发模式:
- 单文档、多文档:应用程序(实现程序初始化)、框架(管理程序的各个窗口)、视图(用户区和数据展示窗口、一个程序可以有多个)、文档(程序的数据)的窗口程序模式——单文档、多文档应用程序。
- 对话框:简化单文档、多文档中框架和文档,集中于窗口元素(按钮、文本框、编辑框、列表等等)的可视化设计模式。
初衷是,MFC告诉你写代码的地方(类向导里的消息响应),你去实现自己的功能吧,就这么简单!
后果是,初学者看到这些”自动“生成代码,被”框架“ 搞得不知所措,战战兢兢,不敢下手。也就是照葫芦画瓢,照写几个范例而已。
因此,了解MFC的实现机制,MFC与SDK编程的关系,掌握这个只能被接受的框架,才能打开程序员设计的思路,放开手脚,自由地实现自己的功能。(推荐书:《深入浅出MFC》)
MFC机制
窗口编程 WinMain 函数是少不了,编译型的程序,入口总是避免不了。创建MFC程序(单文档、多文档、对话框)后在工程中并没发现WinMain函数,程序流程就搞不清楚了。
MFC在程序编译时自动补上WinMain函数,函数的实现在VS的安装目录,比如
C:\Program Files (x86)\Microsoft Visual Studio 11.0\VC\atlmfc\src\mfc (VS2012)
代码是这样的:
// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.
#include "stdafx.h"
#include "sal.h"
/
// Standard WinMain implementation
// Can be replaced as long as 'AfxWinInit' is called first
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:
#ifdef _DEBUG
// Check for missing AfxLockTempMap calls
if (AfxGetModuleThreadState()->m_nTempMapLock != 0)
{
TRACE(traceAppMsg, 0, "Warning: Temp map lock count non-zero (%ld).\n",
AfxGetModuleThreadState()->m_nTempMapLock);
}
AfxLockTempMaps();
AfxUnlockTempMaps(-1);
#endif
AfxWinTerm();
return nReturnCode;
}
/
AfxWinMain 就是WinMain函数(F12可以看定义,_tWinMain等等可以忽略),可能是为了避免MFC ”非纯面向对象“ 程序设计框架(纯面向对象语言是没用全局变量、全局函数),隐藏主函数也是一种办法吧!
开始入口:
CWinThread* pThread = AfxGetThread();
CWinApp* pApp = AfxGetApp();
CWinThread 是 CWinApp 的父类,可以暂时不关心。
CWinApp 其实是创建MFC框架时,应用程序类的父类:
并且,在应用程序的源文件中,可以找到一个全局对象的定义:
AfxGetApp() 的函数功能可以猜到了吧,父类的指针 指向 派生类的 对象!
多态的思想,这时候父类指针调用的函数 就 ”自动地切换“ 到程序员写的代码里去了。
否则,MFC这么知道程序员定义了一个什么类?怎么去调用这个类定义的函数作为程序的切入点?
AfxWinInit 函数在同层目录下的appinit.cpp文件里,这个函数的功能可以忽略。
下面就是InitApplication,派生类没有改写,调用 CWinApp 类(同层目录appcore.cpp),这个可以忽略。
重点当然是 InitInstance,这个函数在派生类中进行了改写,那么转入派生类应用程序类中的函数调用:(以单文档为例,其他都类似)
BOOL CMFC01App::InitInstance()
{
// 如果一个运行在 Windows XP 上的应用程序清单指定要
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
//则需要 InitCommonControlsEx()。否则,将无法创建窗口。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);
CWinApp::InitInstance();
// 初始化 OLE 库
if (!AfxOleInit())
{
AfxMessageBox(IDP_OLE_INIT_FAILED);
return FALSE;
}
AfxEnableControlContainer();
EnableTaskbarInteraction(FALSE);
// 使用 RichEdit 控件需要 AfxInitRichEdit2()
// AfxInitRichEdit2();
// 标准初始化
// 如果未使用这些功能并希望减小
// 最终可执行文件的大小,则应移除下列
// 不需要的特定初始化例程
// 更改用于存储设置的注册表项
// TODO: 应适当修改该字符串,
// 例如修改为公司或组织名
SetRegistryKey(_T("应用程序向导生成的本地应用程序"));
LoadStdProfileSettings(4); // 加载标准 INI 文件选项(包括 MRU)
// 注册应用程序的文档模板。文档模板
// 将用作文档、框架窗口和视图之间的连接
CSingleDocTemplate* pDocTemplate;
pDocTemplate = new CSingleDocTemplate(
IDR_MAINFRAME,
RUNTIME_CLASS(CMFC01Doc),
RUNTIME_CLASS(CMainFrame), // 主 SDI 框架窗口
RUNTIME_CLASS(CMFC01View));
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;
}
比较重要的是所谓的”三口组“,CSingleDocTemplate(单文档模板)捆绑框架、视图、数据,一起生成对象。这里面当然最核心的问题就是:窗户函数,涉及到消息的处理,这也是程序员非常需要关注的。
要找窗口函数,肯定要找窗口类的注册,查找的过程可以参考《深入浅出MFC》,结论是:从WINFRM.CPP 文件的Create 到 WINCORE.CPP 里的 CreateEx函数:
BOOL CWnd::CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTR lpszWindowName, DWORD dwStyle,
int x, int y, int nWidth, int nHeight,
HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam)
{
ASSERT(lpszClassName == NULL || AfxIsValidString(lpszClassName) ||
AfxIsValidAtom(lpszClassName));
ENSURE_ARG(lpszWindowName == NULL || AfxIsValidString(lpszWindowName));
// allow modification of several common create parameters
CREATESTRUCT cs;
cs.dwExStyle = dwExStyle;
cs.lpszClass = lpszClassName;
cs.lpszName = lpszWindowName;
cs.style = dwStyle;
cs.x = x;
cs.y = y;
cs.cx = nWidth;
cs.cy = nHeight;
cs.hwndParent = hWndParent;
cs.hMenu = nIDorHMenu;
cs.hInstance = AfxGetInstanceHandle();
cs.lpCreateParams = lpParam;
if (!PreCreateWindow(cs))
{
PostNcDestroy();
return FALSE;
}
AfxHookWindowCreate(this);
HWND hWnd = CreateWindowEx(cs.dwExStyle, cs.lpszClass,
cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy,
cs.hwndParent, cs.hMenu, cs.hInstance, cs.lpCreateParams);
#ifdef _DEBUG
if (hWnd == NULL)
{
TRACE(traceAppMsg, 0, "Warning: Window creation failed: GetLastError returns 0x%8.8X\n",
GetLastError());
}
#endif
if (!AfxUnhookWindowCreate())
PostNcDestroy(); // cleanup if CreateWindowEx fails too soon
if (hWnd == NULL)
return FALSE;
ASSERT(hWnd == m_hWnd); // should have been set in send msg hook
return TRUE;
}
然后又转回WINFRM.CPP 等等,这个过程对初学者而言可以忽略,核心问题是了解MFC的程序流程。
wndcls.lpfnWndProc = DefWindowProc; 这就是答案。
后续《深入浅出MFC》中很大篇幅来讲解MFC的消息处理机制(三种类型消息),这个也暂时可以忽略。类向导-》消息响应、虚函数的使用就比较清晰了:
可以不问来由,只管使用了,希望简单点,直接响应!否则改写DefWindowProc函数抓取所有窗口消息,也非常的灵活淡定!