☰
VS2022 MFC 入门:手把手生成你的第一个 Windows 窗口程序
2026/10/6 6:25:17 网站建设 项目流程

简介:面向MFC零基础开发者与C++初学者的VS2022 MFC编程入门教程,基于鸡啄米系列内容整理,重点解决从纯C++语法学习过渡到可视化窗口程序开发的核心问题。资源为docx文档,共1个文件,压缩包仅33KB,轻便易下载,方便随时查阅;当前已有4554人学习。文档不仅介绍VC++工具平台与C++语言的关系,还对比VC++6.0、VS2005与VS2022等版本差异,并详细讲解MFC作为微软基础类库如何封装Windows API与简化窗口、工具栏、菜单的生成管理。针对实际编码,教程演示VS2022下使用MFC向导创建单文档应用程序的完整流程,包含解决方案与工程的概念梳理,帮助读者理解HelloWorld等自动生成工程的文件组织。整体内容从概念到实操分层递进,零基础读者也能按图索骥搭建出基本界面程序,为后续开发复杂图形应用打下扎实基础。

1. 还在用 VC++6.0 写窗口程序?这份 VS2022 MFC 入门资料值得你重新过一遍

很多刚学完 C++ 语法的朋友,第一次想写出“带窗口的程序”,搜到的教程还停留在 VC++6.0 时代。照着老教程操作,打开 VS2022 发现界面完全对不上,连 MFC 模板都找不到,更别提什么类向导、消息映射了。这份鸡啄米的 VS2022 MFC 编程入门文档,是用 VS2022 完整走一遍可视化编程的路线:从 VC++ 和 MFC 的概念区别,到用向导生成单文档程序框架,再到拆解工程文件结构、分析运行机制和消息映射。它解决的是“C++ 语法学会了但不知道怎么写窗口程序”这个断层问题,适合刚入门想看到第一个窗口的新手,也适合被老教程坑过、想迁移到 VS2022 的开发者。

2. VS2022 与 MFC 的选型逻辑:为什么还在用 VC++6.0 的人该换环境了

2.1 先把概念理清:C++、VC++、VS2022、MFC 分别是四样东西

很多初学者会把 C++ 和 VC++ 混为一谈,其实这俩根本不是一回事。C++ 是一门语言,定义语法和标准;而 VC++ 是微软提供的开发工具平台,包含编辑器、调试器和编译器,通常集成在 Visual Studio 里。VS2022 就是 Visual Studio 2022 版本,是目前最新的 IDE。MFC 全称是 Microsoft Foundation Classes,也就是微软基础类库,它用 C++ 把 Windows SDK 里的结构和功能封装了一遍,还提供了一套应用程序框架,让开发者不用关心窗口注册、创建、消息循环这些琐碎工作。

如果你在 VS2022 里新建工程时看到 MFC 相关模板,实际上是在用 C++ 语言、在 VS2022 这个 IDE 里、借助 MFC 这套类库来写 Windows 桌面程序。四层是递进关系:语言 -> 工具 -> 集成环境 -> 类库。理解这个关系之后再去看工程的自动生成代码,就不会被一堆类名绕晕。

文档里专门举了个例子说明老版本的问题:for(int i = 0; i < 5; i++)这种写法,在 VC++6.0 里循环结束后变量i仍然可以使用,但这明显不符合 C++ 标准对变量生存期的规定。VC++6.0 先于 C++ 标准推出,语法支持比较差,而 VS2022 对各版本 C++ 标准的支持已经非常完善了。

提示:选型时别只看“轻量”。VC++6.0 确实启动快、占资源少,但它是 1998 年的东西,对现代 C++ 标准的支持是硬伤。你写出的代码在 VS2022 里可能直接编译不过。

2.2 版本怎么选:VS2022 的安装与组件选择

文档给出的建议很明确:优先用 VS2022,因为类库和开发技术最完善;如果机器配置确实低,退而求其次用 VS2005,也比 VC++6.0 强得多。VS2022 的安装文件 2G 多,安装后占 3G 多空间,对处理器和内存要求偏高,这是事实。但现在的开发机基本都能扛住这个负载。

实际安装时有个常见做法值得注意:别用默认安装,单独勾选需要的组件。在 Visual Studio Installer 里选择“使用 C++ 的桌面开发”工作负载,这一项会包含 C++ 编译器、Windows SDK 和 MFC 相关组件。如果你发现新建工程时找不到 MFC 模板,八成就是装 VS2022 的时候没勾这个负载。MFC 组件在工作负载下属于可选项,一般默认会带上;如果没带,可以在“单个组件”里搜索 MFC 勾选。

安装完成后,新建工程时的模板路径是Installed Templates -> Visual C++ -> MFC,里面有三个选项:MFC ActiveX Control、MFC Application 和 MFCDLL。实战中用到最多的是 MFC Application,用来生成可执行的窗口程序;MFCDLL 用来生成动态链接库,一般做插件或模块封装才会用;ActiveX Control 现在基本属于历史遗留技术了,建议直接忽略。

2.3 桌面开发选 MFC 还是 Qt:一个诚实的判断角度

不少人在社区里问“桌面软件开发用 MFC 还是 Qt”。文档本身没有对比过 Qt,但从工程实践角度说几句:MFC 是微软自家的框架,和 Windows API 结合紧密,文档齐全,招聘市场里维护老项目的岗位很多;Qt 是跨平台的,界面写起来更现代。如果你的目标是快速上手 Windows 原生窗口程序、看明白老代码,MFC 仍然值得学;如果你的场景是跨平台产品或者界面要求特别高的新项目,Qt 更合适。

我的观点是:MFC 的价值在于理解 Windows 消息驱动模型和文档视图架构,这个底子打好了,后面转 Qt 也好、转 C# WinForms 也好,对“窗口程序怎么组织”的理解都能平移。所以这份入门资料的定位是打底子,不是终点。

3. 生成单文档应用程序框架的完整流程:从 New Project 到 Ctrl+F5 出窗口

3.1 新建 MFC Application 的关键选项

打开 VS2022 后,点菜单栏File -> New -> Project,左侧展开Visual C++ -> MFC,选 MFC Application。注意对话框下面三个设置项:Name 是工程名,Location 是解决方案存放路径,Solution Name 是解决方案名称。默认情况解决方案名和工程名一致,比如都是 HelloWorld,这样生成的目录结构最清晰。如果解决方案名和工程名不一致,会多套一层目录,新手容易找错文件。

接下来注意这些选项,每个都有实际影响。第一个是 Application type,四种类型分别是 Single document(单文档)、Multiple document(多文档)、Dialog based(基于对话框)和 Multiple top-level documents。新手一般选 Single document 就行,它生成的是类似记事本的单一主窗口;对话框程序适合工具类小软件,窗口只有一个对话框面板。第二个是 Use of MFC,有两个选项:Use MFC in a shared DLL 和 Use MFC in a static library。文档明确建议新手用默认的 shared DLL 方式,因为生成的 exe 体积小;但发布时要把相关 MFC 动态库带上,否则在没装 VS2022 的机器上跑不起来。

3.2 六步向导里的每个选择都有后果

MFC 向导一共要过六步,每一步都有讲究。

第一步 Application Type 前面说过了。第二步 Document Template Properties 可以设置文件扩展名和窗口标题,默认就行。第三步 Database Support 有四个选项:None、Header files only、Database view without file support、Database view with file support。不是做数据库项目的话直接选默认的 None,选了后面的选项会自动生成数据库相关的类和视图,没用上反而干扰学习。

第四步 User Interface Features 控制有没有最大化最小化按钮、系统菜单、初始状态栏,还可以选择普通菜单工具栏还是 Ribbon 风格。第五步 Advanced Features 包括打印和打印预览、最近文件列表个数等。第六步 Generated Classes 会列出将要生成的 4 个核心类:应用类 CHelloWorldApp、主框架窗口类 CMainFrame、文档类 CHelloWorldDoc、视图类 CHelloWorldView。这里可以改类名和基类,比如视图类的基类默认是 CView,也可以换成 CScrollView、CEditView 等,新手阶段用默认就好。

这些选项选完点了 Finish,向导就会自动生成完整的单文档应用框架,并在解决方案浏览器里打开。整个过程不需要写一行代码,你就拥有一个带菜单栏、工具栏、状态栏的空窗口程序了。

3.3 编译运行:理解 Debug 和 Release 的区别

生成完框架后,点菜单Build -> Build HelloWorld,或者直接Debug -> Start Without Debugging(快捷键 Ctrl+F5)。如果直接按 Ctrl+F5,会弹一个对话框问要不要编译,选 Yes,VS2022 会自动编译链接然后运行程序。

首次编译会比较慢,因为要生成预编译头。以后每次改动代码再编译就快多了。这里有个值得提前说清的知识点:编译方式分 Debug 和 Release。Debug 版本的可执行文件包含调试信息,可以设断点单步跟踪;Release 版本没有调试信息,体积更小但没法调试。在 VS2022 工具栏上有配置下拉框,默认是 Debug。新手阶段保持 Debug 就好。

如果你不习惯用 IDE 菜单操作,也可以直接用命令行编译。打开 VS2022 自带的开发者命令行工具,进入工程所在目录执行:

msbuild HelloWorld.sln /t:Build /p:Configuration=Debug /p:Platform=x64

这条命令等价于在 IDE 里点 Build 菜单。/t:Build指定目标是编译,/p:Configuration=Debug指定编译配置,/p:Platform=x64指定平台架构。需要说明的是,如果你的工程生成的是 Win32 平台,把 Platform 参数改成Win32即可。用命令行编译的好处是方便自动化打包,但日常学习阶段在 IDE 里操作就够了。

4. 工程文件的组成结构与运行流程:把 HelloWorld 的骨架拆开看

4.1 六类文件清单:哪些能删、哪些别动

用向导生成框架后,在设置的 Location 下会出现一个以解决方案名命名的文件夹,里面嵌套着工程相关文件。文档把所有这些文件分成六类,我用表格整理一下,方便对照着看:

类别文件/目录作用注意事项
解决方案相关.sdf、.sln、.suo、ipch 目录智能提示、错误提示、代码恢复、团队本地仓库;.sln 存储解决方案设置.sdf 和 ipch 很占空间,不想生成可以在 VS 选项里关闭
工程相关.vcxproj、.vcxproj.filters工程设置、解决方案浏览器里的虚拟目录结构别手动删,影响工程加载
头文件和源文件HelloWorld.h/.cpp、MainFrm.h/.cpp、HelloWorldDoc.h/.cpp、HelloWorldView.h/.cpp 等应用类、主框架、文档类、视图类的实现这是工程主体,以后写代码基本都在这些文件里
资源文件res 目录、HelloWorld.rc、Resource.h图标、菜单、字符串表、加速键表、About 对话框定义.rc 文件是资源脚本,VS 有图形化编辑器可以直接打开
预编译头stdafx.h、stdafx.cpp、HelloWorld.pch把常用 MFC 头文件预编译一次,加快编译速度不要轻易改动 stdafx.h 里的包含列表
编译生成文件Debug/Release 目录exe 和中间文件工程目录下的 Debug 是中间文件,解决方案目录下的 Debug 才是 exe 所在

这六类里面,新手最容易混淆的是两个 Debug 目录。工程文件夹下那个 Debug 存放编译过程中产生的 .obj 等中间文件,解决方案文件夹下的 Debug 才是最终的 exe 可执行文件。我见过不止一个学员在工程目录下翻找 exe 找不到,最后发现原来在上一层目录里。

资源文件这块值得多说一句。HelloWorld.rc 里定义了这个程序所有的资源:菜单长什么样、工具栏有哪些按钮、About 对话框用的什么图标、快捷键表怎么映射。VS2022 里双击 .rc 文件可以直接打开资源视图,拖拽就能改菜单和对话框,这比手写资源脚本直观得多。

4.2 从 WinMain 到消息循环:MFC 框架是怎么跑起来的

上一讲看到框架代码觉得很懵很正常。MFC 程序并不是从main函数开始的,而是从WinMain函数开始的。为了说清楚这事,文档给了个办法:拿一个纯 SDK 的 Windows 程序和 MFC 框架做对比。

纯 SDK 程序的结构是这样的:

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR appName[] = TEXT("HelloWorld"); WNDCLASS myWin; myWin.cbSize = sizeof(myWin); myWin.style = CS_HREDRAW | CS_VREDRAW; myWin.lpfnWndProc = myWndProc; myWin.hInstance = hInstance; myWin.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); myWin.lpszClassName = appName; // 注册窗口类 RegisterClass(&myWin); // 创建窗口 HWND hWindow = CreateWindow(appName, appName, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL); ShowWindow(hWindow, iCmdShow); UpdateWindow(hWindow); // 消息循环 MSG msg; while (GetMessage(&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return (int)msg.wParam; }

这段代码展示了 Windows 程序的完整生命周期:进入 WinMain -> 初始化 WNDCLASS 并注册窗口类 -> 创建窗口 -> 显示并更新窗口 -> 进入消息循环。消息循环是整个程序的心脏,系统或用户的操作会以消息的形式进入消息队列,然后 GetMessage 取出消息、DispatchMessage 分发到窗口过程函数去处理。Windows 程序本质上是消息驱动的,这一点是理解 MFC 的钥匙。

那么 MFC 是怎么把这一切藏起来的?看 HelloWorld.cpp 里的全局对象定义:CHelloWorldApp theApp;。这个全局对象在程序入口之前就会被构造。随后进入WinMain函数(MFC 内部位于 appmodul.cpp),它调用了AfxWinMain,在AfxWinMain里完成了应用初始化、文档模板注册、主窗口创建和显示,最后调用Run函数进入消息循环。

Run函数的内部逻辑简化后是这样的:

int CWinThread::Run() { // 消息循环,直到收到 WM_QUIT while (true) { if (!PumpMessage()) return ExitInstance(); // 空闲处理:UI 更新等 if (IsIdleMessage(&m_msgCur)) { bIdle = TRUE; LIdleCount = 0; } } }

PumpMessage内部就是调用GetMessage、TranslateMessage、DispatchMessage那套东西,和 SDK 程序里的消息循环本质一样。区别在于 MFC 把窗口过程函数统一为AfxWndProc,它通过CWnd::FromHandlePermanent找到消息对应的 C++ 窗口对象,然后转到 MFC 的消息映射表去查找处理函数。

所以从流程上看,MFC 框架和 SDK 程序的运行路径是高度相似的:初始化 -> 注册创建窗口 -> 显示更新 -> 消息循环。只是 MFC 把这些步骤封装到了框架内部,开发者在向导生成的 InitInstance 里看到的只是文档模板注册和主窗口显示那几行代码。

MFC 程序里还有几个核心类的关系要理清:CHelloWorldApp 是应用类,负责初始化;CMainFrame 是主框架窗口,负责菜单栏、工具栏、状态栏的创建和管理;CHelloWorldDoc 是文档类,负责数据存储和读写;CHelloWorldView 是视图类,负责数据显示和交互。这四个类各司其职,构成了 MFC 文档视图架构的骨架。其他像 ClassView、FileView、OutputWnd 这些面板类都是在主框架窗口上创建的辅助面板,如果你不需要它们,后续可以在框架代码里移除。

5. 避坑与排查:向导、编译、运行阶段的常见问题

5.1 向导阶段的三个坑

坑 1:新建工程时找不到 MFC 模板。现象:在 New Project 对话框里搜不到 MFC Application 模板,只有控制台应用和空项目。原因:安装 VS2022 时没有勾选“使用 C++ 的桌面开发”工作负载,MFC 组件没装。解决:打开 Visual Studio Installer,修改安装,勾选“使用 C++ 的桌面开发”,在右侧组件列表里确认 MFC 相关项被选中,更新安装后重启 VS2022。这个坑几乎每个按老教程操作的人都会踩一次,不是 VS2022 没有 MFC 了,是没装而已。

坑 2:选择 Use MFC in a shared DLL 后,生成的程序在别人电脑上运行报“缺少 mfc140u.dll”。现象:把 Debug 目录下的 exe 单独拷到另一台电脑,双击提示找不到动态链接库。原因:shared DLL 方式的程序运行时需要加载 MFC 的动态链接库,目标机器没有安装这些库。解决:发布时要么把对应的 MFC DLL 一并带上,要么在工程属性里把“Use of MFC”改成“Use MFC in a static library”重新编译,生成独立的 exe。注意静态库方式生成的 exe 体积会大很多,但能免去 DLL 依赖的烦恼。

坑 3:解决方案名和工程名不一致,导致目录结构混乱。现象:创建工程时 Solution Name 填了别的名字,生成后文件散落在多层目录里,找不到该改哪个文件。原因:VS2022 会以解决方案名为目录名建立文件夹,工程文件在其下的子目录中。解决:新手阶段保持默认,让解决方案名和工程名一致;如果已经搞乱了,重新建一个工程最省事,不要试图手动挪文件。文件路径变了会导致工程加载失败,这是纯属给自己找罪受。

5.2 编译运行阶段的常见问题

坑 4:编译后两个 Debug 目录,exe 到底在哪。现象:工程文件夹下有一个 Debug 目录,解决方案文件夹下也有一个 Debug 目录,打开工程目录下的那个只能看到一堆 .obj 文件。原因:两种目录存放的东西不同,工程目录下的 Debug 是编译中间文件,解决方案目录下的 Debug 才是最终生成的可执行文件。解决:直接到解决方案目录下的 Debug 文件夹里找 exe 文件。这个问题的根源是大家对 VS2022 目录结构不熟,看完上一章的文件清单就能分清。

坑 5:首次编译特别慢,像卡死了一样。现象:新建的 MFC 工程第一次点 Build,长达几分钟没有反应,CPU 占用满。原因:首次编译要生成预编译头文件 HelloWorld.pch,需要把 stdafx.h 里包含的所有 MFC 头文件整体编译一遍,这个过程不可避免。解决:不用管它,等编译完成即可。以后每次修改代码再编译时会快得多,因为预编译头已经生成好了。如果实在受不了,可以关掉智能提示来减少后台负载,但我不建议为了追求编译速度去动预编译头配置。

坑 6:VC++6.0 老代码迁移到 VS2022 一堆报错。现象:老的 C++ 项目在 VS2022 里编译,报for循环变量作用域错误、头文件找不到等一堆问题。原因:VC++6.0 对 C++ 标准的支持不完整,老代码里充满了i出循环仍可用、隐式类型转换等行为;而且老项目用的头文件路径和 VS2022 不一样。解决:没有一键迁移的办法,只能逐个错误处理。最常见的是把循环变量移到循环外声明,把iostream.h改成带命名空间的<iostream>。这属于老项目迁移的体力活,新手遇到这种情况,建议直接用 VS2022 的 MFC 向导重建工程,把业务逻辑代码拷过来,别在旧工程上死磕。

5.3 运行时排查思路

程序跑起来后如果窗口不出来,先区分是编译问题还是运行问题。编译阶段报错,错误列表里大概率有明确的文件名和行号,照着改就行;编译成功但运行异常,先在 InitInstance 里加断点看是否进了初始化流程,再看消息映射有没有生效。MFC 程序的问题大多集中在消息处理函数没被调用,这时候检查三样东西:消息映射表里有没有加入口项、函数声明有没有用afx_msg前缀、函数实现有没有符合签名要求。这三步是 MFC 消息处理的铁三角,漏一步都不行。

6. 消息映射机制与消息处理函数:自己动手加一个菜单响应

6.1 消息映射表拆解:BEGIN_MESSAGE_MAP 到 END_MESSAGE_MAP 之间有什么

MFC 处理消息靠的是一张消息映射表,说白了就是“消息值 -> 处理函数”的对应关系表。向导生成的 CMainFrame 类里有这样一段代码:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) ON_WM_CREATE() ON_COMMAND(ID_VIEW_CUSTOMIZE, &CMainFrame::OnViewCustomize) ON_COMMAND_RANGE(ID_VIEW_APPLOOK_WIN_2000, ID_VIEW_APPLOOK_WINDOWS_7, &CMainFrame::OnApplicationLook) // 其他消息入口项 END_MESSAGE_MAP()

这段代码的意思是:BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间是消息映射入口项。ON_WM_CREATE()把 WM_CREATE 消息映射到 OnCreate 函数;ON_COMMAND(ID_VIEW_CUSTOMIZE, ...)把菜单命令 ID_VIEW_CUSTOMIZE 映射到 OnViewCustomize;ON_COMMAND_RANGE则处理一个连续的 ID 范围。当窗口收到消息时,MFC 在这个表里查找对应的处理函数,找到就调用,找不到就走默认处理流程。和 SDK 编程里用switch-case逐个判断消息值相比,这种映射表的写法把消息和函数的对应关系集中管理,清晰很多。

消息映射宏的参数含义要记清楚:ON_COMMAND第一个参数是命令 ID,对应菜单项或按钮的资源 ID,第二个参数是处理函数的地址;ON_WM_CREATE这类标准消息宏不需要参数,函数名是约定俗成的 OnCreate、OnClose、OnPaint 等。

6.2 手动给 HelloWorld 添加一个消息处理函数

要在 MFC 里响应一个菜单点击,手动添加需要做三步。假设我们在菜单里加了一个 ID 为ID_MY_TEST的菜单项,希望在点击时弹出一个提示框,步骤是这样的。

第一步,在类定义的结尾处加函数声明,注意前缀必须是afx_msg:

// MainFrm.h 中类定义的结尾处 protected: afx_msg void OnMyTest(); DECLARE_MESSAGE_MAP()

第二步,在类实现文件的消息映射表里加入口项:

BEGIN_MESSAGE_MAP(CMainFrame, CFrameWndEx) ON_WM_CREATE() ON_COMMAND(ID_MY_TEST, &CMainFrame::OnMyTest) END_MESSAGE_MAP()

第三步,在类实现文件里写函数体:

void CMainFrame::OnMyTest() { MessageBox(_T("菜单响应成功")); }

三步缺一不可。声明不在类里编译器直接报错;映射项不写,函数永远不会被调用,这是 MFC 新手最容易犯的错,写好了函数却不生效;函数体不写则链接报错。我当年的血泪经验就是第二步老忘,页面点菜单一点反应都没有,后来形成习惯:每加一个消息响应,先检查映射表里有没有那一行。

从那以后我每次新增消息响应,都强制自己走一遍“声明 -> 映射 -> 实现”三步检查,从没再翻过车。这套 MFC 消息映射机制虽然老,但理解了它,你去读任何 RIBBON 界面或者自定义控件的代码都能很快上手。希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询