技术演进中的开发沉思-32 MFC系列:生命周期

今天,我们继续MFC以一种更亲近的方式,梳理这个框架的脉络,看看一个 MFC 程序从诞生到运行的完整故事。

一、MFC 类层次结构

昨天已经梳理过MFC的类层次了,今天梳理其生命周期,还是要提一下。因为它确实很重要,如果把 MFC 比作一个庞大的家族,那类层次结构就是它的族谱。最顶层的 CObject 就像家族的老祖宗,所有成员都流淌着它的血液 —— 封装了最基础的功能,比如对象的创建与销毁、序列化等。往下分,就像家族里的各个分支:有负责窗口操作的 CWnd 分支,有处理文档的 CDocument 分支,有管理应用程序的 CWinApp 分支。

记得当年第一次看到 MFC 的类结构图时,密密麻麻的继承关系让我犯了晕,就像拿到一本复杂的族谱,分不清谁是叔伯谁是堂兄。后来慢慢明白,这种层次分明的结构,其实是为了让每个类专注于自己的职责。比如 CFrameWnd 是窗口的 “大家长”,负责窗口的框架;CMDIChildWnd 则是它的 “孩子”,专门处理多文档界面中的子窗口。这种分工明确的家族体系,让代码的复用和维护变得简单,就像家族里的手艺代代相传,每个后代只需在前辈的基础上添砖加瓦。

二、所需函数库与头文件

编写 MFC 程序,就像做一道复杂的菜肴,离不开各种 “食材” 和 “调料”—— 函数库与头文件。最核心的 “主食材” 是 mfc42.dll(或后续版本),它就像做菜用的主料,包含了 MFC 的核心实现。而头文件则像各种调料,afxwin.h 是窗口相关的基础调料,afxext.h 是扩展控件的特殊调料,少了一样,菜的味道就不对了。

曾编译 MFC 程序时,因为漏了包含 afxwin.h,编译器报了一堆错误,就像做菜时忘了放盐,再好的食材也难以下咽。从那以后,我就记住了这些 “调料” 的重要性,它们是 MFC 程序能够正常 “烹饪” 的基础。

三、简化的 MFC 程序结构

一个简单的 Hello MFC 程序,就像一封有固定格式的家书,有着明确的开头、正文和结尾。它通常包含一个应用程序类(继承自 CWinApp)和一个主窗口类(继承自 CFrameWnd),再加上几句必要的 “问候语”(代码)。

#include 

// 主窗口类

class CMyFrameWnd : public CFrameWnd

{

public:

CMyFrameWnd()

{

Create(NULL, _T("Hello MFC")); // 创建窗口,就像写下家书的标题

}

};

// 应用程序类

class CMyApp : public CWinApp

{

public:

virtual BOOL InitInstance()

{

m_pMainWnd = new CMyFrameWnd(); // 创建主窗口,相当于开始写家书的正文

m_pMainWnd->ShowWindow(m_nCmdShow); // 显示窗口,就像把家书展开

m_pMainWnd->UpdateWindow(); // 更新窗口,如同把家书的字迹弄清晰

return TRUE;

}

};

CMyApp theApp; // 全局应用程序对象,这是家书的信封,必不可少

这段代码虽然简单,却包含了 MFC 程序的基本骨架。就像家书必须有写信人和收信人一样,MFC 程序也必须有这两个核心类,它们共同构成了程序的 “身份标识”。

四、MFC 程序的来龙去脉

要理解 MFC 程序的运行过程,不妨把它比作一个公司的日常运作。CWinApp 就像公司的 CEO,负责整个程序的 “战略规划”—— 从启动到退出的全过程。当程序启动时,CEO(CWinApp)首先到位,调用 InitInstance 方法 “召开首次会议”,决定要创建哪些窗口,就像公司开张时决定要设立哪些部门。

而 CFrameWnd 则像公司的部门经理,负责具体的 “业务开展”—— 创建和管理窗口。它就像部门经理负责打理部门的日常事务一样,处理窗口的创建、显示和消息响应。还有 CDocument 和 CView,它们就像公司里的业务骨干,分别负责数据管理和数据显示,协同工作,让程序能够正常处理用户的数据。

当年我开发一个仿word的文档编辑软件时,深刻体会到这种分工的好处。CWinApp 负责整体流程,CFrameWnd 搭建界面框架,CDocument 管理文档数据,CView 负责显示编辑,就像一个公司各部门各司其职,效率极高。

五、消息映射机制

消息映射机制的核心,在于那张看不见的 “分拣单”—— 消息映射表。就像邮局需要记录每个地址对应的收件人,MFC 也需要通过宏定义,把消息与处理函数绑定在一起。

看一段示例代码:


// 头文件中声明消息处理函数

class CStockDialog : public CDialog

{

DECLARE_MESSAGE_MAP() // 声明消息映射表,如同在邮局登记收件点

public:

afx_msg void OnBtnInStock(); // 入库按钮的处理函数,像个签收员

};

// 源文件中实现消息映射

BEGIN_MESSAGE_MAP(CStockDialog, CDialog)

ON_BN_CLICKED(IDC_BTN_IN, &CStockDialog::OnBtnInStock)

// 把按钮ID(IDC_BTN_IN)与处理函数绑定,好比在分拣单上写下地址与收件人

END_MESSAGE_MAP()

// 具体处理逻辑

void CStockDialog::OnBtnInStock()

{

// 弹出入库成功提示,像签收员收到包裹后给寄件人发回执

AfxMessageBox(_T("商品已入库"));

}

这段代码里,BEGIN_MESSAGE_MAP和END_MESSAGE_MAP就像邮局的分拣系统,ON_BN_CLICKED宏则是具体的分拣规则。当年调试时,我曾把按钮 ID 写错,结果点击按钮毫无反应,就像寄件人写错地址,包裹卡在邮局仓库里,后来对着资源文件核对了三遍才找到问题。

六、空闲时间处理

MFC 的空闲处理,就像值班人员在没接到任务时整理文件。程序运行时,当消息队列空了,OnIdle函数就会被调用,适合做些轻量的后台工作。假设做一个实时监控程序时,用它来更新状态指示灯:


class CMonitorApp : public CWinApp

{

public:

virtual BOOL OnIdle(LONG lCount)

{

// lCount记录空闲次数,像值班人员看了几次时钟

if (lCount == 1) // 第一次空闲时更新指示灯

{

UpdateStatusLight(); // 自定义函数:刷新指示灯状态

}

return CWinApp::OnIdle(lCount); // 交给父类继续处理其他空闲任务

}

};

这就像医院的护士,没病人呼叫时会整理治疗盘(lCount=1),再没事就拖地(lCount=2),始终让系统保持整洁。但要注意别在这里做耗时操作,否则就像值班人员整理文件时突然开始大扫除,来了紧急任务却腾不出手。

其实这两种机制本质上是 “忙时响应” 与 “闲时打理” 的结合 —— 就像经营一家小店,客人来了要热情接待(消息处理),没人时要擦擦柜台、理理货物(空闲处理),如此才能运转得井井有条。当年写程序时没少在这上面栽跟头,后来才明白:好的代码就像会过日子的人,既懂得应对突发状况,也不忘在空闲时做好规划。

七、对话框与控件:商店里的柜台与商品

对话框与控件,就像商店里的柜台和各种商品,是用户与程序交互的 “前台”。对话框是 “柜台”,为控件提供摆放的空间;按钮、编辑框、列表框等控件则是 “商品”,用户通过它们向程序传递指令或获取信息。

比如一个登录对话框,上面的用户名编辑框就像 “填写姓名的表格”,密码框是 “加密的输入区”,登录按钮是 “提交订单的确认键”。用户通过这些 “商品”,完成与程序的交互,就像在商店里挑选商品、付款结账一样自然。

当年我开发第一个带对话框的程序时,看着自己设计的控件布局被成功显示出来,那种成就感就像自己布置的商店开张一样,既兴奋又满足。

最后小结:

回顾 MFC 的这些核心概念,所蕴含的设计思想 —— 封装、继承、消息驱动等,依然影响着今天的编程思想。对于我们这些老程序员来说,MFC 不仅是一种技术,更是一段青春的记忆,它见证了我们从青涩到成熟的成长历程。其实了解 MFC,就像了解编程史上的一段重要篇章,能帮助我们更好地理解现代 GUI 框架的发展脉络,我还会继续梳理,未完待续........

你可能感兴趣的:(熬之滴水穿石,windows,c++)