MFC入门指南:从Windows GUI开发基础到消息映射与文档视图架构实践

📅 2026/8/23 20:40:36
MFC入门指南:从Windows GUI开发基础到消息映射与文档视图架构实践
1. 项目概述为什么今天还要聊MFC如果你是一个刚接触Windows桌面开发的C新手打开搜索引擎扑面而来的可能是Qt、WPF、WinUI 3甚至是Electron。这时你可能会疑惑MFC是什么一个听起来有点“古老”的名字在今天还有学习的必要吗我的答案是对于想深入理解Windows桌面应用“筋骨”的开发者来说MFC不仅有必要而且是一把绝佳的钥匙。MFC全称Microsoft Foundation Classes是微软在90年代初推出的一套C类库用于封装庞杂的Windows API。你可以把它想象成Windows GUI开发的“官方原装脚手架”。直接使用Windows APIApplication Programming Interface写一个带按钮的窗口你需要处理消息循环、注册窗口类、编写窗口过程函数……代码冗长且极易出错。MFC的出现就是用C的“类”把这些繁琐的流程封装起来让你能用面向对象的方式比如继承CButton类来创建和管理界面元素。那么为什么在202X年我们还要从这个“小白级别”的MFC入门开始原因有三理解底层机制MFC的封装比较“薄”它没有完全隐藏Windows的消息驱动机制。学习MFC你能清晰地看到消息是如何从系统传递到你的窗口再被映射到类的成员函数处理的。这种理解是通透的是学习任何其他GUI框架包括Qt的坚实基础。很多现代框架的概念如信号槽其思想源头都能在消息映射中找到影子。维护遗留项目工业、医疗、金融等领域存在大量运行了十几年甚至二十年的核心业务系统它们很多是用MFC开发的。掌握MFC意味着你拥有了维护和更新这些“古董”级但至关重要的资产的能力这在某些行业是极具价值的技能。轻量级原生开发如果你需要开发一个纯粹的、无需依赖.NET运行库如WPF所需、对安装包体积和运行时内存有苛刻要求的Windows原生工具MFC配合静态链接运行时库依然能生成非常精简高效的可执行文件。所以这篇“小白级别入门讲解”的目标不是让你成为MFC专家而是帮你快速搭建起第一个MFC程序并理解其最基本的运作原理为后续可能的深入探索或项目维护扫清最初的障碍。我们将从开发环境搭建开始一步步创建一个带有简单交互的窗口程序。2. 开发环境准备与第一个MFC项目在开始写代码之前我们需要一个合适的“战场”。虽然古老的Visual Studio 6.0是MFC的“黄金搭档”但为了更好的开发体验和兼容性我强烈推荐使用Visual Studio 2019或2022。它们对MFC的支持依然完善并且提供了更现代化的IDE。2.1 安装必要的Visual Studio组件当你安装Visual Studio时在安装程序的工作负载选择页面务必勾选“使用C的桌面开发”这一项。在这个工作负载的右侧点击“安装详细信息”确保其中包含了“用于Windows的C MFC”或类似字样的可选组件。这是MFC库的头文件、库文件以及相关的项目模板的来源。注意如果你已经安装了VS但没装这个组件没关系不用重装。打开Visual Studio Installer找到你已安装的版本点击“修改”然后按照上述步骤添加这个组件即可。安装完成后启动Visual Studio我们就可以创建第一个MFC项目了。2.2 创建并剖析一个基本的MFC应用程序在Visual Studio中点击“创建新项目”。在项目模板搜索框里输入“MFC”你会看到几个选项对于初学者我们选择“MFC应用程序”。项目配置项目名称比如MyFirstMFC。位置选择一个你喜欢的目录。解决方案名称通常和项目名一致即可。 点击“创建”。MFC应用程序向导 这会弹出一个多页的配置向导这是MFC项目的核心配置界面。应用程序类型选择“单个文档”。这是最经典的结构类似于记事本一次只处理一个文档。同时在“项目样式”里我们选择“MFC标准”。至于“文档/视图结构支持”我们先勾选上这是MFC的经典架构虽然初期有点绕但理解了它才能算入门MFC。复合文档支持全部选择“无”我们不需要OLE这类复杂特性。文档模板属性这里可以设置文件扩展名和文件类型名称我们先保持默认。数据库支持选择“无”我们从最简单的开始。用户界面功能这里很重要确保勾选了“使用菜单栏和工具栏”、“使用状态栏”、“使用经典停靠”。这样我们的程序就会有标准Windows程序的样子。高级功能通常保持默认即可可以留意一下“公共控件清单”它确保程序使用新版本的控件样式。生成的类这是最关键的页面向导会列出它即将为你生成的几个核心类。通常包括CMyFirstMFCApp: 派生自CWinApp代表应用程序本身。每个MFC程序有且只有一个此类的对象它负责初始化、启动消息循环。CMainFrame: 派生自CFrameWnd代表程序的主框架窗口它包含了菜单、工具栏、状态栏和客户区。CMyFirstMFCDoc: 派生自CDocument代表文档负责管理程序的数据。CMyFirstMFCView: 派生自CView代表视图负责在客户区显示文档的数据并处理用户与数据的交互。 理解“文档-视图-框架”这个结构是理解MFC的关键。简单比喻CMainFrame是画框CMyFirstMFCView是画布CMyFirstMFCDoc是画布上要呈现的那幅画的数据源。点击“完成”Visual Studio会自动生成一个完整的、可编译运行的MFC应用程序框架。直接按F5运行你会看到一个标准的Windows窗口带有菜单、工具栏和状态栏。虽然它什么具体功能都没有但你已经成功搭建了一个MFC应用骨架3. MFC核心机制深度解析消息映射与文档视图架构让程序跑起来只是第一步理解其内在的“发动机”如何工作才是入门的关键。MFC的核心魔法在于两点消息映射和文档-视图架构。3.1 消息映射GUI程序的神经传导系统Windows程序是事件驱动或消息驱动的。用户点击鼠标、按下键盘、移动窗口操作系统都会产生一个“消息”并投递到对应应用程序的消息队列中。应用程序的主循环在CWinApp::Run()中不断从队列中取出消息并分发给对应的窗口去处理。直接使用API时你需要在一个巨大的switch-case语句窗口过程函数WndProc里处理各种消息。MFC通过“消息映射”将其优雅地转化为类的成员函数。它是如何工作的打开你的项目找到CMyFirstMFCView.cpp文件你会看到这样一段代码BEGIN_MESSAGE_MAP(CMyFirstMFCView, CView) ON_WM_PAINT() // 标准打印命令 ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_DIRECT, CView::OnFilePrint) ON_COMMAND(ID_FILE_PRINT_PREVIEW, CView::OnFilePrintPreview) END_MESSAGE_MAP()这就是一个消息映射表。BEGIN_MESSAGE_MAP和END_MESSAGE_MAP定义了映射表的范围。中间每一行都是一个消息映射宏。ON_WM_PAINT()这是一个“标准Windows消息映射宏”。它告诉MFC当WM_PAINT需要重绘窗口客户区的消息到来时自动调用CMyFirstMFCView::OnPaint()成员函数。你不需要自己写函数声明MFC已经在基类中预定义了。ON_COMMAND(ID_FILE_PRINT, CView::OnFilePrint)这是一个“命令消息映射宏”。它处理来自菜单项、工具栏按钮或快捷键的命令。ID_FILE_PRINT是“文件-打印”菜单项的ID当用户点击它就会调用CView::OnFilePrint这个函数。实操添加一个鼠标左键点击的消息处理假设我们想在视图窗口中点击鼠标时在点击位置显示一个字符串。在“类视图”中右键点击CMyFirstMFCView类选择“属性”。在属性窗口中点击顶部的“消息”图标一个小闪电。在消息列表中找到WM_LBUTTONDOWN鼠标左键按下从右侧的下拉框中选择“添加 OnLButtonDown”。Visual Studio会自动做三件事在CMyFirstMFCView类的头文件中添加函数声明afx_msg void OnLButtonDown(UINT nFlags, CPoint point);afx_msg是一个标识宏无实际作用。在CMyFirstMFCView.cpp的消息映射表中添加一行ON_WM_LBUTTONDOWN()。在CMyFirstMFCView.cpp文件中生成一个空的OnLButtonDown函数实现。现在你可以在生成的OnLButtonDown函数里编写代码了void CMyFirstMFCView::OnLButtonDown(UINT nFlags, CPoint point) { // TODO: 在此添加消息处理程序代码和/或调用默认值 CClientDC dc(this); // 获取当前视图的设备上下文 CString str; str.Format(_T(你点击了坐标 (%d, %d)), point.x, point.y); dc.TextOut(point.x, point.y, str); // 在点击位置输出文字 CView::OnLButtonDown(nFlags, point); // 调用基类处理这是一个好习惯 }再次运行程序在窗口客户区点击鼠标你就会看到坐标被显示出来。这个过程完美展示了MFC如何将原始Windows消息转化为面向对象的函数调用。3.2 文档-视图架构数据与显示的分离这是MFC中另一个核心设计模式旨在将程序的数据管理文档和数据显示/交互视图分离。一个文档可以对应多个视图例如同一个数据既用表格显示又用图表显示实现了数据的一致性。文档 (CDocument)负责数据的加载、保存序列化和管理。你可以把它想象成程序的后台数据模型。它通过UpdateAllViews(NULL)函数来通知所有关联的视图“数据变了你们该更新显示了”。视图 (CView)负责显示文档中的数据并接收用户输入以修改数据。它通过GetDocument()函数获取指向关联文档的指针从而访问数据。当文档数据更新后视图的OnUpdate()或OnDraw()函数会被调用以刷新显示。框架窗口 (CFrameWnd)作为视图的容器提供菜单、工具栏、状态栏等UI框架。在自动生成的项目里当你点击“文件”-“新建”时应用程序对象会创建一个新文档和一个主框架窗口框架窗口内包含一个视图视图与文档关联。这个关系在应用初始化时就建立好了。实操心得初学者常混淆该在哪里写代码。一个简单的原则与数据直接相关的操作如计算、数据结构的修改放在文档类里与界面显示、鼠标键盘交互相关的操作放在视图类里。例如一个绘图程序图形的坐标点列表应该存储在文档类中而根据这些点绘制线条的代码则在视图类的OnDraw函数中。4. 对话框与控件的使用实现用户交互除了主窗口对话框是另一个最重要的用户界面元素。MFC提供了两种主要对话框模态对话框和非模态对话框。模态对话框会阻塞其父窗口的输入必须关闭后才能操作主窗口如“打开文件”对话框非模态对话框则不会如“查找”对话框。4.1 创建并显示一个模态对话框我们以一个简单的“关于”对话框为例演示完整流程。插入对话框资源在“资源视图”中右键点击你的项目资源如.rc文件选择“添加资源”。选择“Dialog”点击“新建”。这时会出现一个空白的对话框模板和一个“工具箱”窗口。从工具箱拖拽一个“静态文本”Static Text控件到对话框上双击它将标题Caption改为“这是我的第一个MFC对话框程序”。拖拽一个“按钮”Button控件将ID改为IDOK标题改为“确定”。系统默认的“确定”按钮就是用来关闭对话框的。创建对话框类在对话框模板上右键选择“添加类”。输入类名例如CAboutDlg。基类选择CDialogEx它比CDialog有更现代的外观。点击“完成”。在代码中显示对话框我们通常在“帮助”菜单的“关于”项中触发这个对话框。在资源视图中打开菜单资源IDR_MAINFRAME在“帮助”菜单下找到“关于”项其ID通常是ID_APP_ABOUT。我们需要为这个菜单项添加命令处理。在“类视图”中右键点击CMyFirstMFCApp类因为“关于”通常是应用级命令选择“属性”-“消息”找到ID_APP_ABOUT的命令 (ON_COMMAND) 事件选择“添加 OnAppAbout”。Visual Studio会自动生成OnAppAbout函数。我们需要修改它来显示我们的对话框。首先在MyFirstMFC.cpp文件顶部包含对话框类的头文件#include AboutDlg.h。然后修改生成的CMyFirstMFCApp::OnAppAbout()函数void CMyFirstMFCApp::OnAppAbout() { CAboutDlg aboutDlg; // 创建对话框类实例 aboutDlg.DoModal(); // 以模态方式显示对话框 }运行程序点击“帮助”-“关于”就能看到你自定义的对话框了。4.2 为对话框添加功能关联控件与变量一个只有显示的对话框意义不大。我们创建一个简单的登录对话框演示如何获取用户输入。设计对话框新建一个对话框ID改为IDD_LOGIN_DLG。添加两个静态文本“用户名”和“密码”。添加两个编辑框Edit Control分别放在对应文本后面。将密码框的编辑框属性中的“Password”设为True这样输入会显示为星号。添加“登录”和“取消”按钮ID分别为IDOK和IDCANCEL。为这个对话框创建类例如CLoginDlg。为控件关联变量这是MFC里非常方便的功能。在对话框编辑器中右键点击“用户名”编辑框选择“添加变量”。弹出“添加成员变量向导”。“类别”选择“Value”表示我们要关联一个数据值而不是控件对象本身。“变量类型”选择CString。“变量名”填写m_strUsername。点击“完成”。用同样的方法为密码编辑框添加一个CString类型的变量m_strPassword。关键一步在CLoginDlg类的头文件中你会看到这两个变量被声明了。同时在CLoginDlg类的实现文件中找到DoDataExchange(CDataExchange* pDX)函数。你会看到类似下面的代码被自动添加void CLoginDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Text(pDX, IDC_EDIT1, m_strUsername); // IDC_EDIT1是用户名框的资源ID DDX_Text(pDX, IDC_EDIT2, m_strPassword); }DoDataExchange函数和DDX_Dialog Data Exchange系列宏负责在对话框控件和其关联的成员变量之间交换数据。当对话框显示时 (DoModal返回前)如果UpdateData(TRUE)被调用数据会从控件读入变量当对话框初始化或需要刷新控件时调用UpdateData(FALSE)数据会从变量写入控件。使用输入的数据通常我们在“登录”按钮IDOK的处理函数中验证数据。MFC已经为IDOK按钮默认关联了OnOK虚函数。在“类视图”中右键CLoginDlg类选择“属性”-“重写”找到OnOK添加重写。在生成的CLoginDlg::OnOK()函数中编写代码void CLoginDlg::OnOK() { // 将控件中的数据更新到成员变量m_strUsername和m_strPassword中 UpdateData(TRUE); // 简单的验证 if (m_strUsername.IsEmpty() || m_strPassword.IsEmpty()) { AfxMessageBox(_T(用户名和密码不能为空)); return; // 验证失败不关闭对话框 } // 这里可以添加真正的验证逻辑比如与数据库比对 if (m_strUsername _T(admin) m_strPassword _T(123456)) { AfxMessageBox(_T(登录成功)); CDialogEx::OnOK(); // 调用基类OnOK关闭对话框并返回IDOK } else { AfxMessageBox(_T(用户名或密码错误)); // 清空密码框让用户重新输入 m_strPassword.Empty(); UpdateData(FALSE); // 将空密码更新回控件 } }在需要显示登录对话框的地方比如主菜单的一个项创建CLoginDlg实例并调用DoModal()。如果用户点击“登录”且验证成功DoModal()会返回IDOK此时你可以通过对话框对象的成员变量m_strUsername来获取输入的用户名。注意事项UpdateData(TRUE/FALSE)是对话框数据交换的核心。务必理解其调用时机。在构造函数中初始化成员变量后如果想在对话框显示时控件就有初始值需要在OnInitDialog()函数对话框初始化函数中调用UpdateData(FALSE)。而获取用户输入则在OnOK或其他确认函数中调用UpdateData(TRUE)。5. 绘图与GDI基础在窗口中绘制图形MFC使用Windows的图形设备接口GDI或其后继者GDI进行绘图。所有绘图操作都需要通过一个“设备上下文”Device Context, DC对象来完成。DC可以理解为一张画布它关联了窗口、打印机等输出设备并封装了画笔、画刷、字体等绘图工具。5.1 基本的绘图流程绘图代码通常写在视图类的OnDraw(CDC* pDC)成员函数中。当窗口需要重绘如首次显示、被其他窗口遮挡后恢复、手动调用Invalidate()等时框架会自动调用此函数。void CMyFirstMFCView::OnDraw(CDC* pDC) // pDC是框架传进来的设备上下文指针 { CMyFirstMFCDoc* pDoc GetDocument(); // 获取关联的文档指针 ASSERT_VALID(pDoc); if (!pDoc) return; // 示例1画一条从(50, 50)到(200, 200)的红色直线 CPen redPen(PS_SOLID, 2, RGB(255, 0, 0)); // 创建红色实线画笔宽度2 CPen* pOldPen pDC-SelectObject(redPen); // 将新画笔选入DC并保存旧画笔 pDC-MoveTo(50, 50); pDC-LineTo(200, 200); pDC-SelectObject(pOldPen); // 恢复旧画笔重要 // 示例2画一个填充蓝色的矩形 CBrush blueBrush(RGB(0, 0, 255)); // 创建蓝色画刷 CBrush* pOldBrush pDC-SelectObject(blueBrush); pDC-Rectangle(100, 100, 300, 250); // 左上角(100,100)右下角(300,250) pDC-SelectObject(pOldBrush); // 恢复旧画刷 // 示例3显示文本 pDC-SetTextColor(RGB(0, 128, 0)); // 设置文本颜色为深绿色 pDC-SetBkMode(TRANSPARENT); // 设置背景模式为透明 pDC-TextOut(150, 300, _T(Hello, MFC!)); }关键点资源管理GDI对象如CPen,CBrush,CFont是系统资源使用后必须妥善管理。SelectObject会返回一个指向之前对象的指针务必保存并在绘图结束后恢复或者使用MFC提供的CGdiObject自动选择类如CPen的局部变量在栈上析构时会自动恢复旧对象但显式恢复是更清晰的做法。坐标系统默认以客户区左上角为原点(0,0)X轴向右Y轴向下与数学坐标系不同。双缓冲绘图如果绘制复杂图形时出现闪烁需要使用双缓冲技术。原理是先在一个内存DC中绘制完整图像然后一次性拷贝到屏幕DC。这是进阶内容但非常重要。5.2 响应鼠标进行交互式绘图结合之前讲的消息映射我们可以实现一个简单的鼠标画线程序。在视图类中添加三个成员变量用于记录绘图状态class CMyFirstMFCView : public CView { // ... private: CPoint m_ptStart; // 线条起点 CPoint m_ptEnd; // 线条终点 BOOL m_bDrawing; // 是否正在绘制 // ... };在视图类的构造函数中初始化m_bDrawing FALSE;。处理WM_LBUTTONDOWN,WM_MOUSEMOVE,WM_LBUTTONUP消息。OnLButtonDown: 记录起点设置绘制标志。void CMyFirstMFCView::OnLButtonDown(UINT nFlags, CPoint point) { m_ptStart point; m_ptEnd point; m_bDrawing TRUE; SetCapture(); // 捕获鼠标确保即使鼠标移出窗口也能收到消息 CView::OnLButtonDown(nFlags, point); }OnMouseMove: 如果正在绘制擦除旧线条通过异或模式R2_NOT绘制新线条更新终点。void CMyFirstMFCView::OnMouseMove(UINT nFlags, CPoint point) { if (m_bDrawing) { CClientDC dc(this); int nOldMode dc.SetROP2(R2_NOT); // 设置为异或模式第二次画同一线条会擦除它 dc.MoveTo(m_ptStart); dc.LineTo(m_ptEnd); // 擦除旧的预览线 m_ptEnd point; dc.MoveTo(m_ptStart); dc.LineTo(m_ptEnd); // 绘制新的预览线 dc.SetROP2(nOldMode); // 恢复原来的绘图模式 } CView::OnMouseMove(nFlags, point); }OnLButtonUp: 绘制最终线条到设备上下文并存储在文档数据中如果需要持久化结束绘制。void CMyFirstMFCView::OnLButtonUp(UINT nFlags, CPoint point) { if (m_bDrawing) { m_bDrawing FALSE; ReleaseCapture(); // 释放鼠标捕获 CClientDC dc(this); dc.MoveTo(m_ptStart); dc.LineTo(point); // 绘制最终线 // 可选将线条数据m_ptStart, point添加到文档类的数据容器中 // CMyFirstMFCDoc* pDoc GetDocument(); // pDoc-AddLine(m_ptStart, point); // pDoc-UpdateAllViews(NULL); // 通知其他视图更新 } CView::OnLButtonUp(nFlags, point); }这样一个简单的交互式画线功能就完成了。这展示了如何将消息处理与绘图结合实现动态的GUI交互。6. 常见问题与排查技巧实录即使按照步骤操作初学者在MFC实践中也难免会遇到各种问题。这里记录了一些典型“坑点”和解决思路。6.1 编译与链接错误问题error LNK2001: 无法解析的外部符号 “public: virtual __thiscall ...或error LNK2005: “private: static struct AFX_MSGMAP ...。排查这是最常见的链接错误通常与消息映射有关。确保头文件包含正确在.cpp文件中必须包含其对应类声明的头文件并且该头文件包含了基类的头文件。检查消息映射宏确保在.cpp文件中的BEGIN_MESSAGE_MAP和END_MESSAGE_MAP宏之间所有消息映射条目如ON_WM_PAINT(),ON_COMMAND(...)的类名和基类名正确无误。清理并重建有时IDE的中间文件会出错。尝试“生成”-“清理解决方案”然后重新生成。检查预编译头确保所有.cpp文件的第一行都是#include stdafx.h如果使用了预编译头。顺序错误会导致编译问题。问题fatal error C1083: 无法打开包括文件: “afxwin.h”: No such file or directory。排查项目没有正确配置为使用MFC。右键点击项目 - “属性”。在“配置属性”-“常规”中查看“项目默认值”下的“MFC的使用”。应设置为“在共享DLL中使用MFC”或“在静态库中使用MFC”。前者生成文件小但需要目标机器有MFC运行库后者生成文件大但可独立运行。6.2 运行时错误与调试问题程序运行时崩溃调试器停在afxwin*.inl或wincore.cpp等MFC内部文件中错误可能是“访问冲突”。排查空指针/野指针这是C程序的通病。仔细检查所有通过GetDocument(),GetParent()等函数获取的指针是否有效使用ASSERT_VALID或if (pPtr ! nullptr)。资源未释放检查GDI对象CPen,CBrush,CFont,CBitmap是否成对使用了SelectObject并恢复了旧对象。也可以考虑使用CPen等类的局部变量利用RAII资源获取即初始化机制自动管理。消息映射函数签名错误如果你手动添加了消息处理函数务必确保其函数签名与MFC预期的完全一致。例如OnLButtonDown必须是afx_msg void OnLButtonDown(UINT nFlags, CPoint point);。一个错误的参数类型就可能导致栈破坏和崩溃。问题对话框上的控件显示为空白或者变量交换数据失败。排查检查控件ID确保在代码中引用的控件ID如IDC_EDIT1与对话框资源编辑器中的ID完全一致。大小写敏感。检查DoDataExchange确认DDX_或DDV_数据验证宏中的控件ID和成员变量名正确。UpdateData调用时机记住UpdateData(TRUE)从控件读数据到变量UpdateData(FALSE)从变量写数据到控件。在对话框的OnInitDialog中初始化变量后调用UpdateData(FALSE)来初始化控件显示。在OnOK中先调用UpdateData(TRUE)获取用户输入。6.3 设计逻辑问题问题在文档类里修改了数据但视图没有更新。解决在文档类中修改数据后必须调用UpdateAllViews(NULL)来通知所有关联的视图。视图类需要重写OnUpdate函数来响应这个通知或者依赖OnDraw在下次重绘时从文档获取新数据。// 在文档类中 void CMyFirstMFCDoc::ModifyData() { // ... 修改数据 ... SetModifiedFlag(TRUE); // 标记文档为“已修改” UpdateAllViews(NULL); // 通知所有视图更新 } // 在视图类中 void CMyFirstMFCView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { // 可以在这里根据lHint和pHint进行局部更新优化 // 对于简单情况直接强制重绘整个视图 Invalidate(); // 这会触发OnDraw }问题非模态对话框不显示或显示异常。解决非模态对话框对象不能是局部变量否则离开作用域就被销毁了。通常需要将其创建为堆对象new并在对话框关闭时如重写OnCancel和OnOK调用DestroyWindow()而不是EndDialog()最后在合适的时机如对话框的PostNcDestroy函数中delete this;来销毁自身。管理非模态对话框的生命周期需要格外小心避免内存泄漏或访问已销毁对象。6.4 实用调试技巧使用TRACE宏在调试版本中TRACE(_T(“调试信息: %d\n”, someValue));可以将信息输出到Visual Studio的“输出”窗口非常适合跟踪程序流程和变量值且不影响发布版本。利用断言ASSERT(condition)和ASSERT_VALID(pObj)是MFC程序员的好朋友。它们在调试版本中检查条件如果失败则中断程序并提示能快速定位逻辑错误。在关键位置如函数入口检查指针使用断言。检查资源ID资源ID菜单、对话框、控件、字符串是简单的整数但Visual Studio会通过resource.h文件给它们定义有意义的宏。确保代码中使用的宏与resource.h中的定义一致。不一致的ID是许多运行时错误的根源。MFC入门之旅到这里就告一段落了。从环境搭建到理解消息映射和文档视图架构再到使用对话框和进行基本绘图你已经走过了最开始的、也是最核心的一段路。记住MFC的学习曲线前期可能有些陡峭因为它暴露了更多Windows编程的底层细节但正是这些细节能让你对Windows桌面开发有更扎实、更透彻的理解。当你再去看Qt、WinForms这些更上层的框架时你会明白它们在背后为你做了什么从而能更得心应手地使用它们。