尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

MFC老代码实战:VC6工程在VS2019上的编译、拆解与复用

MFC老代码实战:VC6工程在VS2019上的编译、拆解与复用 简介《精通MFC》随书光盘中的完整源代码资源适合正在学习MFC框架与Windows程序开发的C开发者以及需要参考经典实例的进阶读者。资源以书内章节为线索涵盖面向对象、窗口与消息映射、对话框、文档/视图结构、GDI/GDI绘图、进程与线程、动态链接库、COM组件及.NET托管扩展等核心主题代码结构完整、注释清晰可对照书本逐一编译运行帮助理解MFC内部机制与典型用法。压缩包共1114个文件以h头文件、cpp源文件为主并包含vcproj工程文件、rc资源描述、sln解决方案、图标位图等辅助素材整体仅8.01MB目录清晰、按章节组织便于检索和选择性学习。目前已有500余人学习下载适合希望通过经典工程代码夯实MFC基础、快速上手Windows桌面开发的读者。 如果你手头有一张“精通MFC光盘”老光盘打开后看到一堆按章节排好的源代码目录你大概率做过这么一件事把每个示例都编译一遍发现有的能跑、有的报错折腾几回之后就扔进硬盘角落吃灰了。我当年就是这么干的等真正需要自己写MFC程序时才后悔当初没有好好挖一挖这套“精通MFC光盘源代码”。今天这篇不说大道理只讲我怎么把一套老掉牙的VC6工程代码在Windows 10上用VS2019重新编译跑通、拆开读懂、把里面能用的东西抄进自己项目里的全过程。这套代码的年代感很强但MFC这套框架的基本盘没有变消息映射、文档视图、控件自绘、对话框编程、数据库访问至今仍是Windows桌面开发绕不开的东西。对还在用C写桌面工具的人、刚接手老项目维护的人、以及想从源码层面理解MFC机制的人这篇都值得花十分钟看完。1. 这套老源码的真正价值不是Demo合集而是一本能“运行”的书1.1 它到底给了我什么很多人第一眼看到光盘里的目录结构觉得这就是一堆零散示例看完就删了。但仔细看会发现这些源代码是按章节组织的基本覆盖了MFC从入门到进阶的全部主线窗口创建、消息映射、对话框资源、控件交互、文档视图架构、序列化、数据库、网络、多线程。这些示例的共同特点是“小而完整”。每个项目都能独立编译运行代码量控制在几百行到两三千行之间正好是能“一口气读懂”的体量。我做项目有一个体会大工程的代码没法拿来练手信息量太大全是指标和逻辑而这套代码里每个示例解决一个具体问题主题聚焦读起来像看一篇有开头有结尾的文章。我实际从里面重新榨出来的东西有这几类标准的消息映射写法以及ON_MESSAGE、ON_NOTIFY这些宏在什么场景下用自绘按钮、自绘列表框的完整实现这是老代码里最接近“现代控件外观”的部分文档视图架构中Document和View如何完成数据与显示的分离控件的动态创建和布局计算在课堂作业里很难见到完整写法一些冷门但实用的API组合比如WM_CTLCOLOR换色、定时器实现动画、PostMessage做线程间通信。1.2 大多数人没吃透的卡点我不是第一次接触这套代码。几年前刚拿到的第一反应是打开某个示例工程按F7编译然后被各种报错劝退。后来回头看问题出在三个地方按从头到尾的顺序读代码读到一半就忘了前面在干什么只编译不修改代码跑起来了但其实什么都没学会遇到编译错误就上网搜复制过去能编译就完事从不深究为什么报错。其实这套东西的学习路径应该是先让代码跑起来锁定一条完整链路然后把代码改坏、改好、再扩展。我这次就是先解决了编译环境问题再选择几个代表性工程做拆解最后把里面的自绘按钮代码原样抄进了一个新项目效果立竿见影。2. 从VC6到VS2019老工程复活与三处必改的代码2.1 环境准备与升级向导这套光盘里的工程文件是.dsw和.dsp格式对应VC6时代。我用的VS2019社区版第一次打开.dsp文件时编译器会弹出一个升级向导提示将工程转换为新的.sln格式。这里有一个容易忽略的问题转换前最好把整个源码目录复制一份备份因为升级向导会改写原有工程文件一旦转换失败原文件可能就找不回来了。选择“转换为新格式”后通常一路“Next”就能完成。如果直译不成功操作路径是文件→打开→CMake/其他项目或者手动新建一个MFC项目再把源文件整体拖进去。第二种方法更适合“源代码很多但工程文件损坏”的情况。升级完成后还要确认两件事。第一项目属性里的“MFC的使用”要选择“在共享DLL中使用MFC”或“在静态库中使用MFC”不能选“不使用MFC”否则afxwin.h都找不到。第二目标平台版本要选本机已安装的Windows SDKVS2019自带的部分SDK版本可能和老代码里某些API声明对不上。2.2 必须处理的三类编译错误老代码更新到新编译器报错基本集中在三类我整理了对照表报错信息根因处理方法error C2664: cannot convert argument 1 from const char [N] to LPCTSTR新工程默认Unicode字符集老代码用窄字符串项目属性→常规→字符集改为“使用多字节字符集”error C2065: i未声明的标识符VC6允许在for循环里声明变量新标准要求提前声明把for(int i0;...)改成int i; for(i0;...)error C4996: GetVersionEx was declared deprecatedAPI被视为已过时项目属性→预处理定义添加_CRT_SECURE_NO_WARNINGS或改用新版API这里最值得说的是字符集问题。VS2019默认使用Unicode字符集而“精通MFC”光盘里的代码是ANSI时代的产物字符串处理大量依赖char[]和CString直接编译会出现几十甚至上百个类型转换错误。我一开始试图把所有char改成wchar_t工作量太大后来直接在工程属性里切回“使用多字节字符集”问题立刻少了一大半。2.3 关于字符集先跑起来再谈规范这里有个取舍逻辑老项目的第一步是“跑起来”而不是“改规范”。如果要把代码改成Unicode除了字符串类型还要处理CString的隐式转换、文件读写编码、数据库连接字符串等属于一次大重构。我建议初学者先切多字节字符集把逻辑跑通等完全理解了代码结构再考虑兼容性改造。但要注意实战项目里新写代码还是尽量用UnicodeWindows 10/11桌面环境对Unicode支持更完善文件路径、国际化字符串都更安全。老代码是“学习材料”怎么方便怎么来新项目是“生产代码”怎么稳健怎么来。升级完成后我建议先挑一个最简单的对话框示例编译运行确认窗口能弹出来再往后推进。不要一上来就开OpenGL、数据库这些重依赖项目环境问题叠加业务问题会让人崩溃。3. 读源码的正确顺序先找入口再顺着两条线拆3.1 先找到程序真正的“门”很多初学者打开MFC代码会晕因为找不到main函数。MFC把入口封装在框架内部程序员看到的第一段逻辑是全局对象定义和InitInstance。这是读懂整个工程的关键入口。以标准的对话框程序为例theApp这个全局对象在程序启动时先构造随后框架调用InitInstance。你要读的代码顺序是CMyApp theApp; // 全局对象最先构造 BOOL CMyApp::InitInstance() { CMyDlg dlg; // 对话框对象 m_pMainWnd dlg; // 主窗口指针 dlg.DoModal(); // 启动消息循环 return FALSE; }不要跳着读先找到这个文件再打开CMyDlg的构造函数和OnInitDialog界面逻辑基本就串起来了。这个习惯帮我省了大量时间所有光盘里的示例不管功能多复杂都是这个模式只是InitInstance里创建的东西不同。3.2 用两条线去拆解任何示例读对话框程序用“界面线”资源编辑器里的控件布局 → 控件ID → 消息映射宏 → 处理函数。比如光盘里有不少控件处理示例打开资源文件.rc看按钮和编辑框的ID然后在BEGIN_MESSAGE_MAP和END_MESSAGE_MAP之间找到对应入口。读文档视图程序用“数据线”OnNewDocument创建或初始化数据 → 编辑操作修改数据 →OnDraw根据数据绘制界面 →Serialize保存数据。CDocument管数据CView管显示一个改动会同时触发两边联动。我用一个具体例子验证过在某个绘图示例中鼠标点击事件视图类里的OnLButtonDown被记录下来存入一个数组然后调用Invalidate()触发OnDraw重绘。这其实就是一个完整的“事件→数据→界面”循环理解了它你就能扩展到双击、拖拽、定时器刷新等等。3.3 调试器是最好的读者指南读源代码不能只靠眼睛我强烈建议配合调试器。在InitInstance和消息处理函数里各打一个断点按F5运行然后看调用堆栈。你会发现实际执行顺序和你想象的差距很大——比如一个按钮点击事件中间会经过几十层Windows消息分发。有个操作技巧在“调用堆栈”窗口右键选择“显示外部代码”能看见从wWinMain到AfxWinMain再到消息循环的完整路径。看完一遍你对“MFC是Windows API的封装”这句话会有完全不同的理解。4. 值得抄进自己项目的四段源码4.1 自绘按钮的完整套路光盘里最实用的一段代码是自绘按钮。MFC默认按钮样式在Windows 10上特别突兀圆角、渐变、悬停效果全都实现不了。老代码里用CButton派生类重写DrawItem的方式给出了一个极好的参照实现核心逻辑只有二十几行class CMyButton : public CButton { public: virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct); }; void CMyButton::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) { CDC* pDC CDC::FromHandle(lpDrawItemStruct-hDC); CRect rc lpDrawItemStruct-rcItem; BOOL bPushed (lpDrawItemStruct-itemState ODS_SELECTED) ! 0; // 背景填充按下时颜色更深 pDC-FillSolidRect(rc, bPushed ? RGB(120, 160, 200) : RGB(180, 210, 240)); // 绘制边框 pDC-Draw3dRect(rc, RGB(100, 130, 160), RGB(60, 80, 100)); // 居中绘制文字 CString str; GetWindowText(str); pDC-SetBkMode(TRANSPARENT); pDC-SetTextColor(RGB(20, 20, 20)); pDC-DrawText(str, rc, DT_CENTER | DT_VCENTER | DT_SINGLELINE); }使用前记得给按钮加上BS_OWNERDRAW风格否则DrawItem永远不会被调用。这个坑我踩过界面上按钮还在但怎么画都不生效后来发现没在PreSubclassWindow里添加风格。4.2 控件换肤OnCtlColor的另类玩法老代码里做界面配色最方便的入口是OnCtlColor它能统一控制对话框里静态文本、编辑框、列表框的背景色和文字颜色。把它当成“全局皮肤回调”来理解就对了。HBRUSH CMyDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { HBRUSH hbr CDialog::OnCtlColor(pDC, pWnd, nCtlColor); if (pWnd-GetDlgCtrlID() IDC_STATIC_TITLE) { pDC-SetTextColor(RGB(0, 80, 160)); pDC-SetBkMode(TRANSPARENT); return m_brTitle; // 成员变量画刷 } return hbr; }有一个细节容易被忽略返回值必须是HBRUSH而且不能返回局部变量创建的画刷否则控件会拿一个野指针导致刷新时黑块。正确做法是在对话框初始化时创建画刷程序退出时释放。这套代码里正好有完整的画刷管理示例我直接照搬了。4.3 窗口分割让界面不再“一坨”光盘中关于窗口分割的示例把CSplitterWnd的创建时机和参数讲得很明白。在MainFrame的OnCreateClient里添加两行代码就能实现左右分栏m_splitter.CreateStatic(this, 1, 2); m_splitter.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(250, 100), NULL); m_splitter.CreateView(0, 1, RUNTIME_CLASS(CRightView), CSize(500, 100), NULL);真正关键的不是创建代码而是视图类的CView派生关系。如果你想在左边显示树、右边显示列表一定要为这两个区域分别写两个视图类而不是用同一个类硬切。那个示例代码里甚至实现了拖动分隔条时视图自动调整布局这部分逻辑能直接抄进现在的项目里用。4.4 定时器与消息泵异步效果的基础动画、轮询、超时检测这些“看起来在异步执行”的效果底层往往是SetTimer加WM_TIMER消息。光盘里有一个用定时器驱动小球移动的示例虽然简单但能说明核心套路SetTimer(1, 30, NULL); // 每30毫秒触发一次 void CMyWnd::OnTimer(UINT_PTR nIDEvent) { // 更新小球位置 m_nX 5; if (m_nX rcClient.Width()) m_nX 0; Invalidate(); // 触发重绘 CDialog::OnTimer(nIDEvent); }注意定时器消息的优先级不高如果主线程里有大计算量任务动画会卡顿。想真正流畅地做实时渲染还是得配合OpenGL渲染循环或工作线程推送更新。这套老代码没有OpenGL的完整示例但定时器这个基础功必须先吃透。5. 从编译到打包我踩过的坑与验证过的处理方案5.1 缺DLL问题MFC运行库到底怎么分发程序编出来后一运行就弹“缺少mfc140u.dll”的提示这是MFC动态库没带上。处理思路有两条项目属性→常规→MFC的使用→改为“在静态库中使用MFC”生成的exe不再依赖MFC动态库保持动态链接但在安装包中加入VC Redistributable或者把对应DLL和exe放一起。我实测下来给同事演示用的小工具直接静态编译最省心exe体积会从几百KB涨到几MB但换来了“拷过去就能跑”。如果是正式产品建议用安装包方案静态编译有时候会因为多份MFC副本导致模块间类型不一致。5.2 自绘控件不刷新的排查链路有段时间我的自绘按钮总是出现“拖动窗口后按钮变黑”的怪象瞎调好几天。后来整理出一套排查思路能解决大部分类似问题先确认消息有没有收到在DrawItem入口加OutputDebugString或用断点观察是否每次重绘都会进入再确认绘制区域是否正确把背景色改成大红大绿看色块出现在什么位置最后确认是否缺少透明处理自绘控件在对话框重绘时经常需要处理WM_ERASEBKGND返回TRUE避免背景被反复擦成白色。排查链路的逻辑是“先判断消息流再判断绘制代码最后怀疑坐标”。经验证明超过一半的自绘控件问题出在消息根本没到达绘制函数而不是绘制代码本身写错了。5.3 老旧API的兼容处理老代码里用了一些被新SDK标记为deprecated的API比如GetVersionEx、Delphi风格的强制转换。编译时全屏C4996警告会干扰视线我的建议是在项目里统一加_CRT_SECURE_NO_WARNINGS让警告消失而不是逐个改API。等代码稳定运行后再评估是否真的要换新API——有些老API虽然“过时”行为反而符合老代码的预期。同样需要注意的是数据库部分。光盘里的数据库示例主要走ODBC在64位系统上要安装对应架构的ODBC驱动否则连接串能填但连不上。这个不是代码问题是运行环境问题我第一次在64位Win10上跑32位程序时也卡了半天。6. 改造练习把别人的源码变成自己的工具箱6.1 改造一给对话框加入OpenGL渲染区域想验证自己对消息映射的理解最有效的办法是给一个老示例加入OpenGL渲染能力。具体思路是自定义一个CStatic派生类在OnCreate或PreSubclassWindow里创建OpenGL上下文把渲染逻辑挂在OnPaint和OnSize里。void CGLView::OnSize(UINT nType, int cx, int cy) { CStatic::OnSize(nType, cx, cy); HDC hdc ::GetDC(m_hWnd); wglMakeCurrent(hdc, m_hRC); glViewport(0, 0, cx, cy); glClearColor(0.2f, 0.4f, 0.8f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); SwapBuffers(hdc); wglMakeCurrent(NULL, NULL); ::ReleaseDC(m_hWnd, hdc); }这个练习能让你彻底理解“窗口句柄、设备上下文、渲染上下文”三者关系而这些都是MFC封装好的。6.2 改造二让列表框显示数据库查询结果另一个实用练手是把列表和MySQL数据串起来。老代码里的CListBox或CListCtrl都预留了添加行的接口关键在于查询完成后不能跨线程更新UI。正确做法是用PostMessage发送一个自定义消息到主线程让控件在主线程里填充数据。热词里很多人搜“mfc查找mysql”核心难点不在SQL语句而在数据库驱动的libmysql.dll加载路径和字符集转换。我建议先做一个针线活用ODBC接口把查询结果逐行AddString到CListBox验证连接通畅后再封装成类别一上来就套复杂的DAO。6.3 改造三控件随窗口缩放自适应MFC程序最容易被吐槽的就是界面固定大小、拉伸后控件错位。老代码里往往没有自适应函数需要自己重写OnSize。核心思路非常直接保存初始客户区和控件坐标然后在OnSize里按比例重新MoveWindow。void CMyDlg::OnSize(UINT nType, int cx, int cy) { CDialog::OnSize(nType, cx, cy); if (m_btnOK.GetSafeHwnd() m_oldCx 0) { double kx (double)cx / m_oldCx; double ky (double)cy / m_oldCy; CRect rc; m_btnOK.GetWindowRect(rc); ScreenToClient(rc); m_btnOK.MoveWindow((int)(rc.left * kx), (int)(rc.top * ky), (int)(rc.Width() * kx), (int)(rc.Height() * ky)); } }字体大小在窗口放缩后不会自动变如果想连字体一起缩放得借助EnumChildWindows遍历所有子控件这个改造做一遍对子控件、坐标空间、消息传递这些概念的把控会明显上升一个台阶。6.4 怎样算“真正掌握”我的验收标准很朴素把源码放在一边只给需求文档能独立写出一个带菜单、工具栏、状态栏、自绘按钮、列表框数据展示、OpenGL渲染区域的完整窗口程序就算过关。如果写的时候还要反复翻旧代码说明这代码还不是你的。从“精通MFC光盘源代码”里真正带走的不只是那几段可以复制的类而是一种思维习惯看到一个界面效果先想它是通过什么消息触发的再想数据怎么组织最后想如何重绘。这套思考方式今天依然适用Windows平台如此其他GUI框架也大同小异。最后分享一个小技巧读老代码时我会随手给关键函数加注释理由写清楚“这句代码为什么在这里”。等整份源码过一遍这些注释就是你写给自己的技术文档比任何旁人的讲解都深刻。本文还有配套的精品资源点击获取
返回列表