热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

脉络_二.MFC框架程序分析Windows编程课程学习笔记

篇首语:本文由编程笔记#小编为大家整理,主要介绍了二.MFC框架程序分析--Windows编程课程学习笔记相关的知识,希望对你有一定的参考价值。2.1MAF

篇首语:本文由编程笔记#小编为大家整理,主要介绍了二. MFC框架程序分析--Windows编程课程学习笔记相关的知识,希望对你有一定的参考价值。



2.1 MAF APPWizard

MAF APPWizard是一个辅助生成代码的向导工具,可以帮助自动生成基于MFC框架的源代码。创建一个MFC的单文档界面(SDI)应用程序,命名为Windows编程。


2.2 基于MFC的程序框架剖析

查看一下类视图(ClassView)的标签页,可以看到五个非常重要的类。

类名以C开头,打开折叠项,发现他们都是由CObject派生。新奇的查阅一下MSDN中的层次结构图。CWnd类是非常重要的一个类。https://msdn.microsoft.com/zh-cn/library/ws8s10w4.aspx

I.WinMain函数

0.WinMain函数

Win32应用程序中,有1)WinMain函数;2)设计窗口类,注册窗口类,产生窗口,显示窗口,更新窗口;3)消息循环清晰地程序脉络。在MFC中虽然在工程中找不到这样的脉络,但是MS在MFC底层框架类中封装了这些步骤。

Edit->Find inFiles 在MFC安装目录下可以找到这些脉络。


在appmodul.cpp中找到了WinMain类似的函数,_tWinMian,调用AfxWinMain。在_tWinMian处F9下断点,程序停留在_tWinMian处。说明该_tWinMain(HINSTANCEhInstance, HINSTANCEhPrevInstance, _In_ LPTSTR lpCmdLine, int nCmdShow)就是Win32中的WinMain。



1.theApp全局对象

找到了WinMain,那么它是如何将MFC程序的各个类组织到一起的?也就是说,MFC程序中的类是如何与WinMain函数关联起来的?



可以双击CWindows编程App类,跳转到定义的头文件中。CWindows编程App派生于CWinAppEx类(应用程序类),在CWindows编程App构造函数处设置断点,发现比先进入构造函数,在进入WinMain。

// 唯一的一个 CWindows编程App 对象
CWindows编程App theApp;

这里,回顾一下C++四种不同的对象的生与死问题:

①对于一般局部对象(栈Stack中产生):当对象产生时,构造函数被执行;当函数结束时(以至于对象将毁灭),析构函数被执行。

②对于new操作局部对象(堆Heap中产生):当对象产生时(执行new操作),构造函数被执行;当delete对象语句被执行时,析构函数被执行。

③对于全局对象:程序一开始,其构造函数就先被执行(比入口函数main、WinMain更早);程序结束前,析构函数被执行。

④对于局部静态(static)对象:只会有一个实例产生,而且在固定的内存上(既不是stack也不是heap),执行到第一次声明处(也就是在MyFunc第一次调用)时,构造函数被调用;当程序结束时(对象因此遭致毁灭),析构函数被执行,但比全局对象的析构函数先一步执行。

TheAPP对象的构造函数在调用之前,会调用其父类CWinApp的构造函数,从而将自己创建的类与Microsoft提供的基类关联起来。CWinApp的构造函数完成了程序运行时的一些初始化工作。 

//initialize CWinApp state
ASSERT(afxCurrentWinApp == NULL); // only one CWinApp object please
pModuleState->m_pCurrentWinApp = this;
ASSERT(AfxGetApp() == this);

this是theApp。同时CWinApp有一个形参lpszAppName。(CWinApp::CWinApp(LPCTSTRlpszAppName))但CWindows编程App构造函数没有形参,那子类将显式的调用基类带参数的构造函数。然而没有这么做的原因是CWinApp构造函数中设置了NULL。

class CWinApp : public CWinThread
DECLARE_DYNAMIC(CWinApp)
public:

// Constructor
explicit CWinApp(LPCTSTR lpszAppName = NULL); // app name defaults to EXE name

2.AfxWinMain函数

程序调用CWinApp类的构造函数,以及CWindows编程App的构造函数,生成theApp对象后进入WinMain函数。WinMain实际调用AfxWinMain。在文件中找到AfxWinMain定义代码

/
//Standard WinMain implementation
// Can be replaced as long as 'AfxWinInit' iscalled 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))
gotoInitFailure;

//App global initializations (rare)
if (pApp != NULL && !pApp->InitApplication())
gotoInitFailure;

//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();
gotoInitFailure;

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;

调用AfxGetThread得到一个CWinTread类型指针。AfxGetApp得到一个CWinApp指针。AfxGetThread其实调用了AfxGetApp,所以pThread和pApp一致。

_AFXWIN_INLINECWinApp* AFXAPI AfxGetApp()
return afxCurrentWinApp;
#define afxCurrentWinApp AfxGetModuleState()->m_pCurrentWinApp

由CWinApp构造函数返回的是CWinApp中保存的this,所以知道pThread、pApp返回的是theApp全局对象。

3.InitInstance函数

virtual BOOL InitInstance();

虚函数调用的是子类CWindows编程的InitInstance

BOOL CWindows编程App::InitInstance()
// 如果一个运行在 Windows XP 上的应用程序清单指定要
// 使用 ComCtl32.dll 版本 6 或更高版本来启用可视化方式,
//则需要 InitCommonControlsEx()。 否则,将无法创建窗口。
INITCOMMONCONTROLSEX InitCtrls;
InitCtrls.dwSize = sizeof(InitCtrls);
// 将它设置为包括所有要在应用程序中使用的
// 公共控件类。
InitCtrls.dwICC = ICC_WIN95_CLASSES;
InitCommonControlsEx(&InitCtrls);

CWinAppEx::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(CWindows编程Doc),
RUNTIME_CLASS(CMainFrame), // 主SDI 框架窗口
RUNTIME_CLASS(CWindows编程View));
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;

II.MFC框架窗口

1. 设计和注册窗口

MFC已经预设计了一些标准窗口类,只需要AfxEndDeferRegisterClass注册就好。AfxEndDeferRegisterClass在WINCORE.cpp中。AfxEndDeferRegisterClass代码可以知道首先判断窗口类型,赋予类名(wndcls.lpszClassName),调用AfxRegisterClass。AfxRegisterClass首先获得窗口类信息,未注册则调用RegisterClass注册。

在CMainFrame类PreCreateWindow函数设断点,发现程序在调用theApp和WinMain后到达,所以MFC在WinMain后注册窗口类的。

2.创建窗口

在设计和注册窗口类后是创建窗口,由CWnd类的CreateEx函数完成。声明在AfxWin.h中

virtual BOOL CreateEx(DWORD dwExStyle, LPCTSTR lpszClassName,
LPCTSTRlpszWindowName, DWORD dwStyle,
const RECT&rect,
CWnd*pParentWnd, UINT nID,
LPVOIDlpParam = NULL);

代码在wincore.cpp中,调用了CreateWindowEx函数;在MFC底层代码中CFrameWnd类调用了上述CreateEx,而前者又有LoadFrame调用。CFrameWnd派生于CWnd类,CWnd类的不是虚函数,所以CFrameWnd继承而没有重写。

3.显示窗口和更新

m_pMainWnd->ShowWindow(SW_SHOW);
m_pMainWnd->UpdateWindow();

III.消息循环

AfxWinMain中的pThread->Run();CWinThread类的Run函数的定义。

// mainrunning routine until thread exits
int 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())
returnExitInstance();

// 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));


主要结构是一个for循环,在收到WM_QUIT消息退出,在循环中调用了一个PumpMessage。

IV.窗口过程函数

wndcls.lpfnWndProc = DefWindowProc;

该行代码作用是设置窗口过程函数,默认的窗口过程:DefWindowProc。并不是将所有消息交给DefWindowProc处理,而是采取消息映射的机制。

 

至此,了解了MFC程序的整个运行机制。


推荐阅读
  • 本文介绍了数据库的存储结构及其重要性,强调了关系数据库范例中将逻辑存储与物理存储分开的必要性。通过逻辑结构和物理结构的分离,可以实现对物理存储的重新组织和数据库的迁移,而应用程序不会察觉到任何更改。文章还展示了Oracle数据库的逻辑结构和物理结构,并介绍了表空间的概念和作用。 ... [详细]
  • 本文为Codeforces 1294A题目的解析,主要讨论了Collecting Coins整除+不整除问题。文章详细介绍了题目的背景和要求,并给出了解题思路和代码实现。同时提供了在线测评地址和相关参考链接。 ... [详细]
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文介绍了在Python3中如何使用选择文件对话框的格式打开和保存图片的方法。通过使用tkinter库中的filedialog模块的asksaveasfilename和askopenfilename函数,可以方便地选择要打开或保存的图片文件,并进行相关操作。具体的代码示例和操作步骤也被提供。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • 本文介绍了使用kotlin实现动画效果的方法,包括上下移动、放大缩小、旋转等功能。通过代码示例演示了如何使用ObjectAnimator和AnimatorSet来实现动画效果,并提供了实现抖动效果的代码。同时还介绍了如何使用translationY和translationX来实现上下和左右移动的效果。最后还提供了一个anim_small.xml文件的代码示例,可以用来实现放大缩小的效果。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 本文介绍了一个Java猜拳小游戏的代码,通过使用Scanner类获取用户输入的拳的数字,并随机生成计算机的拳,然后判断胜负。该游戏可以选择剪刀、石头、布三种拳,通过比较两者的拳来决定胜负。 ... [详细]
  • HDU 2372 El Dorado(DP)的最长上升子序列长度求解方法
    本文介绍了解决HDU 2372 El Dorado问题的一种动态规划方法,通过循环k的方式求解最长上升子序列的长度。具体实现过程包括初始化dp数组、读取数列、计算最长上升子序列长度等步骤。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文介绍了九度OnlineJudge中的1002题目“Grading”的解决方法。该题目要求设计一个公平的评分过程,将每个考题分配给3个独立的专家,如果他们的评分不一致,则需要请一位裁判做出最终决定。文章详细描述了评分规则,并给出了解决该问题的程序。 ... [详细]
  • 本文介绍了OC学习笔记中的@property和@synthesize,包括属性的定义和合成的使用方法。通过示例代码详细讲解了@property和@synthesize的作用和用法。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • 使用Ubuntu中的Python获取浏览器历史记录原文: ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
author-avatar
石pimentel_958
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有