全面解析MFC应用程序框架

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 对于程序员来说,如果要提高编程效率,一个好用的,功能强大的并且可以得心应手使用的编程工具往往会给我们程序员带来莫大的方便。其实对于现在的编程工具来说,使用哪一种工具都不是问题的关键,重要的是你能够使用到什么程度,毕竟现在的工具都是非常的强大,对于一般的编程任务来说还没有不能够胜任的工具,否则的话恐怕他就不可能在这个世界上存在哪怕是只有一个月的生命。
对于 程序员来说,如果要提高编程效率,一个好用的,功能强大的并且可以得心应手使用的编程工具往往会给我们程序员带来莫大的方便。其实对于现在的编程工具来说,使用哪一种工具都不是问题的关键,重要的是你能够使用到什么程度,毕竟现在的工具都是非常的强大,对于一般的编程任务来说还没有不能够胜任的工具,否则的话恐怕他就不可能在这个世界上存在哪怕是只有一个月的生命。 但是根据个人所好以及周围的人的影响,我们都会去使用某一种或者几种工具。比较Visual Basic 、C++ Builder和Delphi等编程工具,用VC++编写Windows应用程序可以说是最富于挑战性和艰巨性。在本文中我无意去比较各种工具的好坏,仅就我自己学习Visual C++的MFC的体会和心得拿出来与大家一起分享和交流,也希望可以结识更多的志同道合的朋友。

  就我个人的偏见,学习VC++就应该要学习他的类库MFC(Microsoft Foundation Classes)。也许有的人一听说MFC就有点望而生畏,这是可以理解的,毕竟Microsoft虽然给了我们一个强大而且非常复杂的类库,但是没有给我们带来学习他的好的方便之处。回想自己学习MFC时的无助和迷茫,以及所走过的弯路,现在想起来还心有余悸,虽然我现在也还是处于非常初级的初级入门阶段,但是还是很乐意把自己的心得和体会拿出来一起与大家分享。也希望得到大家的指点。

   一、SDK应用程序结构

  我学习MFC之路可是从windows编程开始的(可能这一开始就是弯路了)。首先也请大家跟着我一起看一个SDK应用程序结构的Windows应用程序。当然也是经典的“Hello world!”了,编写的过程就不必罗嗦了,下面给出他的主要源代码(我使用向导生成的,但是为了阅读的方便经过了一点点的编辑):

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine,int nCmdShow)
{
 // TODO: Place code here.
 MSG msg;
 ………………………………
 MyRegisterClass(hInstance);
 if (!InitInstance (hInstance, nCmdShow))
 {
  return FALSE;
 }

 // Main message loop:
 while (GetMessage(&msg, NULL, 0, 0))
 {
  if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
  {
   TranslateMessage(&msg);
   DispatchMessage(&msg);
  }
 }
 return msg.wParam;
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
 ……………………
 return TRUE;
}

//窗口函数WndProc(),回调函数

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 int wmId, wmEvent;
 ……………………
 switch (message)
 {
  case WM_COMMAND:
   ………………
   break;
  case WM_PAINT:
   hdc = BeginPaint(hWnd, &ps);
   // TODO: Add any drawing code here...
   RECT rt;
   GetClientRect(hWnd, &rt);
   DrawText(hdc, szHello, strlen(szHello), &rt, DT_CENTER);
   EndPaint(hWnd, &ps);
   break;
  case WM_DESTROY:
   ……
  default:
   return DefWindowProc(hWnd, message, wParam, lParam);
 }
 return 0;
}


  SDK之"Hello World"源程序

  首先让我们看一下这个程序的结构,WinMain()是函数的入口点,该函数的主要任务是完成一些初始化的工作和维护了一个消息循环。他们的工作流程如下:入口(WinMain())---à MyRegisterClass()---->InitInstance ()-àwhile消息循环。函数由入口开始执行,之后调用 MyRegisterClass()注册窗口类,之后InitInstance ()生成并显示窗口,这样之后,就完成了一个窗口的初始化工作了(当然,在 MyRegisterClass(),InitInstance ()中都需要调用相应的API函数来具体的实现,不过我这里重点分析的是他的结构,所以不考虑他的具体实现细节),然后就是维护消息循环,至此,这个程序的基本结构就差不多建立了。以后程序的运作就靠个消息循环来推动了。

  现在,再让我们看看那个消息循环的结构,在例子程序中,我们是要程序在窗口中输出一句"Hello World"。在主程序中我们似乎已经把应用程序的框架全部分析的滴水不漏了,但是没有看到要求程序输出"Hello World" 呀?这就是Windows消息的作用了,我们当然还记得刚刚我们说过主程序还维持了一个消息循环,不错,就是在这个循环里面大有文章。Window应用程序的特点就是消息驱动,当系统或者用户要求应用程序完成某一个任务的时候,所依靠的就是消息,系统会把用户的要求或者系统的要求放到一个消息结构中,然后发送给应用程序,再去处理。我们现在来看看应用程序是怎么来完成我们的任务的。在应用程序初始化完成之后,调用了一个显示窗口的API函数,所以系统知道了程序要显示窗口了,此时(注意,这里就是产生消息的时机),此时就会在消息队列中产生一个WM_PAINT消息,这样,应用程序的消息循环就可以捕捉到这个消息并且将它发送给窗口函数(注意,这个函数是由系统调用的),然后窗口函数就处理这个消息,我们就是在他处理这个消息的时候让他完成我们的任务的。从这个过程中我们可以看到,如果我们要与程序交互的话,需要做得仅仅就是选择适当的时机让系统产生消息了。现在,我们终于可以完全明白SDK的程序的运作过程了。

  一个SDK的应用程序的框架就这样被建立了,下面我们再来看看如何建立一个MFC的应用程序的框架,以及这两者之间的对应关系。

 

二、 MFC应用程序结构

  在前面我分析了一个经典的SDK应用程序的结构,现在可是要进入主题“MFC应用程序结构”了。MFC应用程序有好多种,为了能够更清楚地与前面的文章形成对比,我们在这里看一个SDI的应用程序,当然例子还是经典的“Hello World”了。在使用向导生成应用程序后,会发现有好几个文件,首先我们不管有哪些文件,按照程序执行得主线抽取主要的源程序分析一下再说(因为MFC生成的应用程序不是很方便阅读,所以在这里我将他们重新编辑了)。

CHelloWorldApp theApp;

int AFXAPI AfxWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,

LPTSTR lpCmdLine, int nCmdShow)
{
 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)
  {
   TRACE0("Warning: Destroying non-NULL m_pMainWnd\n");
   pThread->m_pMainWnd->DestroyWindow();
  }
  nReturnCode = pThread->ExitInstance();
  goto InitFailure;
 }
 nReturnCode = pThread->Run();

 InitFailure:
 ……………………
 AfxWinTerm();
 return nReturnCode;
}

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;
  return TRUE;
}

BOOL CHelloWorldApp::InitInstance()
{
 AfxEnableControlContainer();
 ………………………………
 // Change the registry key under which our settings are stored.
 // TODO: You should modify this string to be something appropriate
 // such as the name of your company or organization.

 SetRegistryKey(_T("Local AppWizard-Generated Applications"));
 LoadStdProfileSettings(); // Load standard INI file options (including MRU)
 // Register the application's document templates. Document templates
 // serve as the connection between documents, frame windows and views.

 CSingleDocTemplate* pDocTemplate;
 pDocTemplate = new CSingleDocTemplate(
  IDR_MAINFRAME,
  RUNTIME_CLASS(CHelloWorldDoc),
  RUNTIME_CLASS(CMainFrame), // main SDI frame window
  RUNTIME_CLASS(CHelloWorldView));

 AddDocTemplate(pDocTemplate);

 // Parse command line for standard shell commands, DDE, file open
 CCommandLineInfo cmdInfo;
 ParseCommandLine(cmdInfo);

 // Dispatch commands specified on the command line
 if (!ProcessShellCommand(cmdInfo))
  return FALSE;
  // The one and only window has been initialized, so show and update it.
 m_pMainWnd->ShowWindow(SW_SHOW);
 m_pMainWnd->UpdateWindow();

 return TRUE;
}

BOOL CWinApp::InitInstance()
{
 return TRUE;
}

  MFC应用程序之“Hello World”

  咋一眼看上去,好像这个程序无从下手分析,甚至连程序的入口点都找不到。其实,上面的程序还是经过整理后才有如此模样。好了,一样的来看看这个程序是怎么运行的吧(要注意的事上面的程序来自于不同的文件,这里排版在一起只是为了更清楚地表示程序的结构,至于MFC的文件组织我会在下面一个话题中具体的分析,这里可以暂时不考虑)。

  首先,在程序的开始处,首先定义了一个全局变量theApp,我们现在只需要知道他代表了整个程序的存在,然后程序开始介入入口点。有没有搞错,入口点在哪里?不及,其实int AFXAPI AfxWinMain()就是这个程序的入口点,奇怪吧!不过没有关系,就好像我们第一次看到C语言中的main()函数一样,只要了解就可以了。在AfxWinMain()中分别调用了一些类的成员函数,仿照前面的分析方法,也可以画出一个程序执行路径图。入口点----〉AfxGetThread()------〉AfxGetApp()-------àAfxWinInit()-------àpApp->InitApplication()-----àpThread->InitInstance()------àpThread->Run()。可以看到,程序一样有一个执行的线索可循,但是,相对于SDK来说,如今已经面目全非了,过去的那种清晰的程序结构在这些程序中也有吗?答案是肯定的,只不过他们的具体实现在MFC中都进行了包装而已,那么,还是来看看这个应用程序是如何启动并且运行的吧。

  程序由AfxWinMain()开始运行后,首先调用了AfxGetApp()来获取应用程序的对象指针pApp,然后通过这个指针调用有关的成员函数来完成初始化和启动工作,最后就调用了Run()函数,在这里,Run()函数就是代表了SDK中的消息循环。

  事情的发展在预料中进行着,但是似乎还遗漏了一点什么似的?不错,在上面我们的确是还有一样工作没有完成,这就是我们需要的”Hello World”好像还没有输出来!这不是我的疏忽,而是故意的安排,因为MFC中采用了一种全新(当然是相对于SDK来说的了)的消息处理机制,至少在表面上来说是这样的。然而,在这里我不打算一下子就把问题解决掉,毕竟这有点复杂,等我们明白了MFC的文件之间的关系后我会在回答这个问题。

  我没有想到这篇文章会这么长,刚刚开始的时候我以为我可以一下子就把这个问题说清楚地,但是事实上我写作的思路也像我分析程序时一样,竟然是一个漫长的过程!所以我也有必要提醒一下自己以及读这篇文章的朋友,应该休息一下了。我现在都开始庆幸我把这个问题分成好几个小问题来解决了,我会在接下来的话题中继续讨论的事MFC程序所生成的文件以及它们之间的调用关系。

 

三、MFC程序结构剖析

  在前面我分别给出了SDK和MFC应用程序的框架,并且稍微理了一下它们之间的对应关系。但是对于MFC程序来说,要想真正搞懂它的框架的话,还是不够的。现在我要做的事就是继续去分析一下上面的MFC程序,期望能够如我的标题所言,明明白白的看透MFC的应用程序框架。

  首先就是看一下应用向导生成的MFC应用程序都有哪些主要的文件,包含哪些类以及相关类的对应功能和他们之间的关系(再次说明,因为我在这里关心的是程序的框架结构,所以对于一些与话题关系不很大的东西我将会略去不提)。

  在上面的SDI应用程序中,向导为我们生成了4个主要的类,这些类都是MFC类的派生类,分别包含在对应的头文件以及实现文件中。

  1、 框架窗口类及其相关的文件

  框架窗口类对应应用程序的主窗口,明白这一点后就可以建立一个对这个类的感性认识了,他的定义是在头文件MainFrm.h中,而实现则是在MainFrm.cpp文件中。所有与框架窗口向光的功能都是在这里定义和实现的。

  2、 文档类及其相关的文件

  文档类在应用程序中没有直观的对应关系,但是,我们应该知道的一点就是MFC的框架的一个特点就是文档/视图结构。这里可以抽象一点地来理解,比如说我们在Word中打开了一个文件,其实,这个文件就是文档,而我们看到的只不过是这个文件一个视图,所以说文档提供了应用程序显示的支持,但是我们真正看到的应用程序显示的则是一个视图,文档类的定义是在“Hello WorldDoc.h”中,而他的实现则是在“Hello WorldDoc.cpp”文件中。

  3、 视图类及其相关的文件

  视图类是用来显示文档对象的内容的,我们在Word程序中所看到的界面就是一个视图,我们要修改,画图首先操作的对象就是视图,所以,视图类就如他的名称所指出的那样,提供了从用户角度看到的东西。视图类的定义在“Hello WorldView.h”中,实现是在文件“Hello WorldView.h”中。

  4、 应用程序类及其相关的文件

  MFC应用程序的初始化,启动运行和结束都是由应用程序对象完成的。他对应的文件是”Hello World.cpp”和”Hello World.h”。

  在大致的了解了应用程序对象的作用之后,现在我们来看看它们之间的关系(如下图所示)。



  从上面的图中可以大致的看出MFC应用程序对象之间的关系了。应用程序一开始(此处的内容可以参考上一篇)生成应用程序对象,然后,在InitInstance中创建中将会创建文档模板对象(通过CdocManager管理),这样一来,应用程序就可以通过建立的模板对象来管理文档、视图、和框架窗口。

  至此,我的这篇文章终于可以暂时告一段落了,在文中我主要分析的是应用程序的框架,所以忽略了很多的实现细节。其实,学习MFC是一个非常漫长的路,处在不同的阶段都会产生不同的看法和认识,相信随着学习的深入,每一个人都会有自己的理解。但是在这里我给出的自己的经验就是:在学习MFC的时候,一定要在宏观上对他的应用程序框架有个感性的认识!因为这样之后,我们就会在学习的时候有个明显的方向,知道自己学习的是什么东西。我在一开始的时候,就没有认识到这一点,所以往往在学习过程中,往往会不知道下一步应该做什么,也不明白自己看的东西是做什么用的。希望通过这篇文章可以与大家一起学习!

目录
相关文章
|
10天前
|
机器学习/深度学习 人工智能 算法
模型无关的局部解释(LIME)技术原理解析及多领域应用实践
在当前数据驱动的商业环境中,人工智能(AI)和机器学习(ML)已成为各行业决策的关键工具,但随之而来的是“黑盒”问题:模型内部机制难以理解,引发信任缺失、监管合规难题及伦理考量。LIME(局部可解释模型无关解释)应运而生,通过解析复杂模型的个别预测,提供清晰、可解释的结果。LIME由华盛顿大学的研究者于2016年提出,旨在解决AI模型的透明度问题。它具有模型无关性、直观解释和局部保真度等优点,在金融、医疗等领域广泛应用。LIME不仅帮助企业提升决策透明度,还促进了模型优化和监管合规,是实现可解释AI的重要工具。
48 9
|
1天前
|
机器学习/深度学习 自然语言处理 JavaScript
信息论、机器学习的核心概念:熵、KL散度、JS散度和Renyi散度的深度解析及应用
在信息论、机器学习和统计学领域中,KL散度(Kullback-Leibler散度)是量化概率分布差异的关键概念。本文深入探讨了KL散度及其相关概念,包括Jensen-Shannon散度和Renyi散度。KL散度用于衡量两个概率分布之间的差异,而Jensen-Shannon散度则提供了一种对称的度量方式。Renyi散度通过可调参数α,提供了更灵活的散度度量。这些概念不仅在理论研究中至关重要,在实际应用中也广泛用于数据压缩、变分自编码器、强化学习等领域。通过分析电子商务中的数据漂移实例,展示了这些散度指标在捕捉数据分布变化方面的独特优势,为企业提供了数据驱动的决策支持。
7 2
信息论、机器学习的核心概念:熵、KL散度、JS散度和Renyi散度的深度解析及应用
|
4天前
|
Web App开发 IDE 测试技术
自动化测试的利器:Selenium 框架深度解析
【10月更文挑战第2天】在软件开发的海洋中,自动化测试犹如一艘救生艇,让质量保证的过程更加高效与精准。本文将深入探索Selenium这一强大的自动化测试框架,从其架构到实际应用,带领读者领略自动化测试的魅力和力量。通过直观的示例和清晰的步骤,我们将一起学习如何利用Selenium来提升软件测试的效率和覆盖率。
|
3天前
|
人工智能 缓存 Java
深入解析Spring AI框架:在Java应用中实现智能化交互的关键
【10月更文挑战第12天】Spring AI 是 Spring 框架家族的新成员,旨在满足 Java 应用程序对人工智能集成的需求。它支持自然语言处理、图像识别等多种 AI 技术,并提供与云服务(如 OpenAI、Azure Cognitive Services)及本地模型的无缝集成。通过简单的配置和编码,开发者可轻松实现 AI 功能,同时应对模型切换、数据安全及性能优化等挑战。
|
2天前
|
自动驾驶 物联网 5G
|
2天前
|
数据处理 Python
Python在音频传输中的应用实例解析
Python在音频传输中的应用实例解析
12 1
|
7天前
|
关系型数据库 数据挖掘 数据库
解析数据库联结:应用与实践中的 INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN 与 CROSS JOIN
解析数据库联结:应用与实践中的 INNER JOIN、LEFT JOIN、RIGHT JOIN、FULL OUTER JOIN 与 CROSS JOIN
15 1
|
16天前
|
移动开发 Android开发 数据安全/隐私保护
移动应用与系统的技术演进:从开发到操作系统的全景解析随着智能手机和平板电脑的普及,移动应用(App)已成为人们日常生活中不可或缺的一部分。无论是社交、娱乐、购物还是办公,移动应用都扮演着重要的角色。而支撑这些应用运行的,正是功能强大且复杂的移动操作系统。本文将深入探讨移动应用的开发过程及其背后的操作系统机制,揭示这一领域的技术演进。
本文旨在提供关于移动应用与系统技术的全面概述,涵盖移动应用的开发生命周期、主要移动操作系统的特点以及它们之间的竞争关系。我们将探讨如何高效地开发移动应用,并分析iOS和Android两大主流操作系统的技术优势与局限。同时,本文还将讨论跨平台解决方案的兴起及其对移动开发领域的影响。通过这篇技术性文章,读者将获得对移动应用开发及操作系统深层理解的钥匙。
|
7天前
|
存储 编译器 C++
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
【C++篇】揭开 C++ STL list 容器的神秘面纱:从底层设计到高效应用的全景解析(附源码)
28 2
|
9天前
|
设计模式 算法 PHP
PHP中的设计模式:策略模式的深入解析与应用
本文将详细探讨PHP中的策略模式,从基本概念到实际应用进行深入分析。通过具体代码示例和应用场景,帮助读者理解并掌握这一设计模式的使用,提升软件开发的灵活性和维护性。

推荐镜像

更多