深入浅出话VC++(2)——MFC的本质.doc_第1页
深入浅出话VC++(2)——MFC的本质.doc_第2页
深入浅出话VC++(2)——MFC的本质.doc_第3页
深入浅出话VC++(2)——MFC的本质.doc_第4页
深入浅出话VC++(2)——MFC的本质.doc_第5页
已阅读5页,还剩14页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

一、引言上一专题中,纯手动地完成了一个Windows应用程序,然而,在实际开发中,我们大多数都是使用已有的类库来开发Windows应用程序。MFC(Microsoft Foundation Class, 微软基础类库)是微软为了简化程序员的开发工作而将Windows API 封装到C+类中,利用这些类,程序员可以有效地完成Windows平台下应用程序的开发。本专题将详细剖析它。二、利用向导创建一个MFC程序用于帮助有效地开发Windows应用程序的类库除了MFC外,还有其他开源类库提供,比如说QT,只是QT不是微软开发的罢了,为了更好地剖析MFC,下面让我们用Visual Studio中的MFC模板和向导工具来创建一个基于MFC的单文档(SDI)应用程序。1. 启动Visual studio 2010,单击文件(FIle)菜单新建项目项目,在出现的项目窗口中选择Visual C+ 语言,然后选择MFC应用程序,并输入项目的名称为SDIMFC,具体如下图所示。 2.输入项目名称后点击确定按钮,将出现MFC应用程序向导窗口,点击下一步,应用程序类型选择:单个文档,如下图所示:3. 点击下一步,出现MFC向导的第三个对话框,复合文档支持保持默认选择,然后在出现的对话框中一直点击下一步来完成一个单文档MFC应用程序的创建。下面,按下Ctrl+F5来运行MFC应用程序, 之后将看到我们创建的MFC应用程序界面,具体如下图所示:在上面的程序中,我们并没有编写任何代码,运行它后就生成了一个带标题栏,系统菜单,具有最大化、最小化框和一个可调边框的应用程序,这一切的工作都是由MFC的向导工具帮我们完成,即该向导工具为我们生成了很多代码,下面就以这个简单的MFC程序来分析下MFC框架。三、MFC框架详细解析我们看下用MFC向导工具帮我们生成的哪些代码。你可以在VS中点击类视图选项卡(如果VS界面上没有看到类视图的, 可以通过菜单栏视图类视图的方式显示出来),就可以看到如下图所示的类。从上图可以发现,在MFC中,类的命名都是以字母“C”开头的,这种命名方式只是一种约定,让开发人员很快识别出该类是否属于MFC类库中的类。从图片可以看到,前面创建的单文档应用程序中有15个类,但这里我们只分析4个基本类,因为这4个基本类是每个Windows应用程序都会包含的,这4个类是:CMainFrame类、C+工程名(SDIMFC)+App类、C+工程名+Doc类(即CSDIMFCDoc类)和CSDIMFCView类(也是C+工程名+View的结构)。这4个类的基类都是MFC中类,基类的查看可以通过在VS类视图点击图标。关于MFC中类图层次结构图可以参考MSDN:/zh-cn/library/ws8s10w4.aspx,下图(摘自MSDN)很好地诠释了MFC中层次结构图类别。3.1 MFC应用程序中的WinMain函数前面对我们创建的MFC应用程序结构进行了一个简单的介绍,下面让我们深入剖析MFC应用程序的实现原理,在前一专题讲到,所有Window下窗口应用程序都要遵循这样一个过程:程序首先进入WinMain函数,然后设计窗口类、注册窗口类、创建窗口、显示和更新窗口、最后进入消息循环,将消息传递给窗口过程函数进行处理。然后在MFC应用程序中,我们使用VS的查找工具在MFC项目中查看WinMain函数却找不到,再查看CreateWindow函数也找不到,那么是不是MFC应用程序不需要WinMain函数,不需要创建窗口吗?这个疑问答案肯定是否定的,因为MFC应用程序一样是Windows应用程序,所以一定遵循上一专题介绍的过程,只是MFC提供的类帮我们对这些类进行了封装,这些函数都存在于MFC的源代码中,下面我们一起去找找程序的入口WinMain函数。既然WinMain函数存在与MFC源码中,自然我们就要知道MFC源码在哪里了,在安装Visual studio的时候,我们已经安装了MFC的源代码,具体路径为:VS的安装路径VCatlmfcsrcmfc,如果你本机把VS安装到D:Program Files(x86)的话,则MFC源代码路径在:D:Program Files (x86)Microsoft Visual Studio 10.0VCatlmfcsrcmfc。 下面利用Windows搜索工具查看WinMain函数的存在那个C+类中,在搜索之前,需要设置下Windows搜索工具,默认情况下,Windows搜索工具搜索内容在没有索引的位置,只搜索文件名的,这里需要设置为搜索文件名和内容,具体设置如下图所示(Win7下选择工具文件夹选项即可显示下图):设置完成之后,在搜索框中输入WinMain,你将看到如下图所示的一个搜索结果:WinMain函数的实现实际在appmodul.cpp文件里,用VS打开该cpp文件,你将看到WinMain函数的定义:extern C int WINAPI_tWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)#pragma warning(suppress: 4985) / call shared/exported WinMain return AfxWinMain(hInstance, hPrevInstance, lpCmdLine, nCmdShow);上面代码中是_tWinMain函数啊,并不是我要的WinMain函数的,难道是找错了吗?对于这个疑问,答案也是否定的,我们没有找错,这里_tWinMain是一个宏定义,按F12即可以看到它代表的是WinMain。宏定义源码如下(存在于tchar.h头文件中):/* Program */#define _tmain main/ 宏定义#define _tWinMain WinMain为了证明我们找到的WinMain正是我们需要找到的入口函数,我们可以在appmodul.cpp文件中_tWinMain函数中设置一个断点,然后按下F5按钮运行SDIMFC程序,我们发现,SDIMFC程序会在我们刚才设置的断点处停下来,具体如下图所示:我们已经找到了WinMain函数在MFC中的实现了,但是并没有弄明白,我们创建的MFC程序是如何调用appmodul.cpp中的_tWinMain函数的,即程序中的MFC类如何与WinMain函数联系起来的呢?下面就让我们看看CSDIMFCApp类(至于为什么想到该类,因为其后缀为App,即应用程序,所以猜测程序在进入WinMain函数之前会先进入该类),在类视图中双击该类将在VS中看到该类的定义,从类定义可以知道,CSDIMFCApp类继承于CWinAppEx类,CWinAppEx类又继承于CWinApp,为了证明在WinMain函数之前先执行了CSDIMFCApp类中代码,我们在CSDIMFCApp类中的构造函数设置一个断点,然后按F5再运行下该程序,将发现程序首先停在CSDIMFCApp类的构造函数处,然后进入到_tWinMain函数(该断点是我们之前设置的断点)。这里又引起另外一个疑问了为什么程序会首先调用CSDIMFCApp的构造函数呢?既然构造函数被调用了,肯定定义了该类的一个对象,然后,我们可以发现在CSDIMFCApp类中,定义了一个CSDIMFCApp类型的全局对象theApp,存在于SDIMFC.cpp文件中,具体定义代码如下:/ 唯一的一个 CSDIMFCApp 对象CSDIMFCApp theApp; / 初始化对象,这种方式为调用类的无参数构造来初始化,所以会调用类的无参构造函数然后我们在这个全局对象处设置一个断点,然后再按F5调试运行下该程序,你将发现程序执行的顺序为:theApp全局对象CSDIMFCApp构造函数(调用派生类的构造函数之前会调用其父类的构造函数)_tWinMain函数。在MFC程序中,theApp对象是用来唯一标识应用程序实例的,每个MFC程序有且仅有一个应用程序对象(这里为theApp对象)。3.2 设计和注册窗口类现在我们已经找到MFC中的WinMain函数了,根据前一专题的内容,接下来就是找到MFC应用程序中的窗口类和注册窗口类的代码,在上一专题中,窗口类和注册都是在WinMain函数中定义的,下面让我们看下MFC中WinMain函数都帮我们封装了什么,在MFC中的WinMain函数中只是简单对AfxWinMain函数进行调用,下面让我们看看AfxWinMain具体代码: 1 / AfxWinMain函数 2 int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 3 _In_ LPTSTR lpCmdLine, int nCmdShow) 4 5 ASSERT(hPrevInstance = NULL); 6 7 int nReturnCode = -1; 8 CWinThread* pThread = AfxGetThread(); 9 CWinApp* pApp = AfxGetApp();10 11 / AFX internal initialization12 if (!AfxWinInit(hInstance, hPrevInstance, lpCmdLine, nCmdShow)13 goto InitFailure;14 15 / App global initializations (rare)16 if (pApp != NULL & !pApp-InitApplication()17 goto InitFailure;18 19 / Perform specific initializations20 if (!pThread-InitInstance()21 22 if (pThread-m_pMainWnd != NULL)23 24 TRACE(traceAppMsg, 0, Warning: Destroying non-NULL m_pMainWndn);25 pThread-m_pMainWnd-DestroyWindow();26 27 nReturnCode = pThread-ExitInstance();28 goto InitFailure;29 / PThread-Run函数是完成消息循环任务的30 nReturnCode = pThread-Run();31 32 InitFailure:33 #ifdef _DEBUG34 / Check for missing AfxLockTempMap calls35 if (AfxGetModuleThreadState()-m_nTempMapLock != 0)36 37 TRACE(traceAppMsg, 0, Warning: Temp map lock count non-zero (%ld).n,38 AfxGetModuleThreadState()-m_nTempMapLock);39 40 AfxLockTempMaps();41 AfxUnlockTempMaps(-1);42 #endif43 44 AfxWinTerm();45 return nReturnCode;46 上面代码首先调用AfxGetThread函数获得一个指向CWinThread类型的指针,然后再调用了AfxGetApp函数获得一个指向CWinApp类型的指针,再继续调用AfxWinInit函数进行AFX(以AFX为前缀的函数为应用程序框架函数,Application Framework)内部初始化,接着pApp调用InitApplication函数,该函数主要完成MFC内部管理方面的工作,该函数为虚函数,在CWinApp中的实现为(函数实现代码查找按照前面介绍的方式进行查看):/ 主要完成MFC内部管理工作BOOL CWinApp:InitApplication() if (CDocManager:pStaticDocManager != NULL) if (m_pDocManager = NULL) m_pDocManager = CDocManager:pStaticDocManager; CDocManager:pStaticDocManager = NULL; if (m_pDocManager != NULL) m_pDocManager-AddDocTemplate(NULL); else CDocManager:bStaticInit = FALSE; LoadSysPolicies(); return TRUE;View Code 接着继续调用pThread的InitInstance函数,按F12可知,该函数声明为虚函数,根据类的多态性,这里AfxWinMain函数中调用的InitInstance函数为调用子类CSDIMFCApp的InitInstance函数,该函数的定义代码为: 1 / CSDIMFCApp 初始化 2 BOOL CSDIMFCApp:InitInstance() 3 4 / 如果一个运行在 Windows XP 上的应用程序清单指定要 5 / 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式, 6 /则需要 InitCommonControlsEx()。否则,将无法创建窗口。 7 INITCOMMONCONTROLSEX InitCtrls; 8 InitCtrls.dwSize = sizeof(InitCtrls); 9 / 将它设置为包括所有要在应用程序中使用的10 / 公共控件类。11 InitCtrls.dwICC = ICC_WIN95_CLASSES;12 InitCommonControlsEx(&InitCtrls);13 14 CWinAppEx:InitInstance();15 16 17 / 初始化 OLE 库18 if (!AfxOleInit()19 20 AfxMessageBox(IDP_OLE_INIT_FAILED);21 return FALSE;22 23 24 AfxEnableControlContainer();25 26 EnableTaskbarInteraction(FALSE);27 28 / 使用 RichEdit 控件需要 AfxInitRichEdit2() 29 / AfxInitRichEdit2();30 31 / 标准初始化32 / 如果未使用这些功能并希望减小33 / 最终可执行文件的大小,则应移除下列34 / 不需要的特定初始化例程35 / 更改用于存储设置的注册表项36 / TODO: 应适当修改该字符串,37 / 例如修改为公司或组织名38 SetRegistryKey(_T(应用程序向导生成的本地应用程序);39 LoadStdProfileSettings(4); / 加载标准 INI 文件选项(包括 MRU)40 41 42 InitContextMenuManager();43 44 InitKeyboardManager();45 46 InitTooltipManager();47 CMFCToolTipInfo ttParams;48 ttParams.m_bVislManagerTheme = TRUE;49 theApp.GetTooltipManager()-SetTooltipParams(AFX_TOOLTIP_TYPE_ALL,50 RUNTIME_CLASS(CMFCToolTipCtrl), &ttParams);51 52 / 注册应用程序的文档模板。文档模板53 / 将用作文档、框架窗口和视图之间的连接54 CSingleDocTemplate* pDocTemplate;55 pDocTemplate = new CSingleDocTemplate(56 IDR_MAINFRAME,57 RUNTIME_CLASS(CSDIMFCDoc),58 RUNTIME_CLASS(CMainFrame), / 主 SDI 框架窗口59 RUNTIME_CLASS(CSDIMFCView);60 if (!pDocTemplate)61 return FALSE;62 AddDocTemplate(pDocTemplate);63 64 65 / 分析标准 shell 命令、DDE、打开文件操作的命令行66 CCommandLineInfo cmdInfo;67 ParseCommandLine(cmdInfo);68 69 70 71 / 调度在命令行中指定的命令。如果72 / 用 /RegServer、/Register、/Unregserver 或 /Unregister 启动应用程序,则返回 FALSE。73 if (!ProcessShellCommand(cmdInfo)74 return FALSE;75 76 / 唯一的一个窗口已初始化,因此显示它并对其进行更新77 m_pMainWnd-ShowWindow(SW_SHOW);78 m_pMainWnd-UpdateWindow();79 / 仅当具有后缀时才调用 DragAcceptFiles80 / 在 SDI 应用程序中,这应在 ProcessShellCommand 之后发生81 return TRUE;82 在上面代码中,77行代码和78行代码为窗口的显示和更新,m_pMainWnd为我们创建的窗口,按F12转到其定义为指向CWnd类型的指针,Cwnd类是MFC为我们预定义的标准窗口类,现在我们已经找到MFC程序中的窗口类,接下来就是找到MFC中是如何注册窗口的,在MFC中,窗口类的注册是由AfxEndDeferRegisterClass函数完成的,其定义在Wincore.cpp文件中,AfxEndDeferRegisterClass函数内部又是通过AfxRegisterClass函数(该函数也定义在wincore.cpp文件中)来注册窗口类,由于篇幅的问题,这里就不贴其函数的定义源码,大家可以在本机中进行查看。3.3 创建窗口我们已经找到了MFC设计窗口和注册的封装,接下来就是MFC程序中是如何创建一个窗口的,该功能在MFC中是由CWnd类的CreateEx函数进行完成的,该函数的声明在afxwin.h文件中,具体代码如下:virtual BOOL CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU nIDorHMenu, LPVOID lpParam = NULL);实现代码位于wincore.cpp文件中,我们程序中创建的是CMainFrame窗口,CMainFrame类继承于CFrameWndEx,该类又继承于CFrameWnd,CFrameWnd类的Create函数内部会调用CreateEx函数,而CFrameWnd的Create函数又由CFrameWnd类的LoadFrame函数调用。CFrameWnd类的Create函数声明位于afxwin.h文件中,其实现代码位于winfrm.cpp文件中,实现代码如下:BOOL CFrameWnd:Create(LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, LPCTSTR lpszMenuName, DWORD dwExStyle, CCreateContext* pContext) HMENU hMenu = NULL; if (lpszMenuName != NULL) / load in a menu that will get destroyed when window gets destroyed HINSTANCE hInst = AfxFindResourceHandle(lpszMenuName, ATL_RT_MENU); if (hMenu = :LoadMenu(hInst, lpszMenuName) = NULL) TRACE(traceAppMsg, 0, Warning: failed to load menu for CFrameWnd.n); PostNcDestroy(); / perhaps delete the C+ object return FALSE; m_strTitle = lpszWindowName; / save title for later if (!CreateEx(dwExStyle, lpszClassName, lpszWindowName, dwStyle, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, pParentWnd-GetSafeHwnd(), hMenu, (LPVOID)pContext) TRACE(traceAppMsg, 0, Warning: failed to create CFrameWnd.n); if (hMenu != NULL) DestroyMenu(hMenu); return FALSE; return TRUE;View Code3.4 显示窗口和更新窗口在CSDIMFCApp类的InitInstance函数内容即有窗口显示和更新窗口的代码,具体代码如下: / 唯一的一个窗口已初始化,因此显示它并对其进行更新 m_pMainWnd-ShowWindow(SW_SHOW); m_pMainWnd-UpdateWindow();3.5 消息循环CWinThread类的Run函数就是完成消息循环这一任务的,该函数在AfxWinMain函数中进行了调用,其定义在thrdcore.cpp文件中,其定义代码如下所示:/ main running routine until thread exitsint CWinThread:Run() ASSERT_VALID(this); _AFX_THREAD_STATE* pState = AfxGetThreadState(); / for tracking the idle time state BOOL bIdle = TRUE; LONG lIdleCount = 0; / acquire and dispatch messages until a WM_QUIT message is received. for (;) / phase1: check to see if we can do idle work while (bIdle & !:PeekMessage(&(pState-m_msgCur), NULL, NULL, NULL, PM_NOREMOVE) / call OnIdle while in bIdle state if (!OnIdle(lIdleCount+) bIdle = FALSE; / assume no idle state / 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) if (IsIdleMessage(&(pState-m_msgCur) bIdle = TRUE; lIdleCount = 0; while (:PeekMessage(&(pState-m_msgCur), NULL, NULL, NULL, PM_NOREMOVE); View Code3.6 窗口过程函数在AfxEndDeferRegisterClass函数中其中有一行这样的代码(下面代码红色标记处):BOOL AFXAPI AfxEndDeferRegisterClass(LONG fToRegister) / mask off all classes that are already registered AFX_MODULE_STATE* pModuleState = AfxGetModuleState(); fToRegister &= pModuleState-m_fRegisteredClasses; if (fToRegister = 0) return TRUE; LONG fRegisteredClasses = 0; / common initialization WNDCLASS wndcls; memset(&wndcls, 0, sizeof(WNDCLASS); / start with NULL defaults / 设置窗口过程函数,这里指定时一个默认的窗口过程 wndcls.lpfnWndProc = DefWindowProc; wndcls.hInstance = AfxGetInstanceHandle(); wndcls.hCursor = afxData.hcurArrow; .但实际上,MFC中并不是把所有消息都交给DefWindowProc这一默认窗口过程进行处理的,而是采用了一种称为消息映射机制来处理各种消息,MFC消息映射机制指的是可以通过类向导为类添加消息处理

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论