ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MFC校验和计算工具开发:从CRC32到MD5的实战指南

MFC校验和计算工具开发:从CRC32到MD5的实战指南 简介一款基于MFC和VC开发的校验和计算小工具在VS2015环境下使用对话框界面实现可对给定数据执行累加和或异或校验计算适合需要数据完整性校验的通信调试场景也适合MFC入门者学习源码。压缩包共41个文件大小约63.52MB其中包含.cpp和.h源代码、MFC项目工程.sln、.vcxproj、.filters、界面资源文件.rc、.rc2、.ico、编译中间产物.tlog、.obj、.pch、.ilk、.pdb以及可直接运行的.exe和ReadMe说明文档结构完整。已有988人学习下载。通过阅读源码可以掌握MFC对话框程序的消息映射机制理解累加和与异或校验的算法实现并可在原工程基础上修改或扩展其他校验方式对数据解析和网络传输调试具有实用参考价值。 最近在VS2013环境下用MFC写了一个校验和计算小工具源码打包成MFCApplicationCheckSum.zip。说实话这个工具本身并不复杂但整个开发过程里踩了不少值得记录的坑从界面布局到文件读取再到校验算法集成每一步都藏着一些新手容易忽略的细节。如果你正准备用MFC做类似的小工具或者只是想了解校验和在实际工程里怎么落地这篇文章应该能帮你省下不少摸索的时间。这个工具解决的痛点是日常开发中经常需要快速验证文件完整性、比对固件版本、确认传输后的数据有没有被篡改。虽然网上有很多命令行工具可以做校验和但图形界面操作起来总归更直观尤其是给非技术同事用的时候一个能拖拽文件、点击按钮就出结果的窗口程序远比敲命令友好得多。所以我用MFC搭了一个最小可用的对话框程序支持常见的CRC32和MD5校验后续还能很方便地扩展SHA系列和SM3国密算法。1. 为什么用MFC写校验和工具需求分析与技术选型1.1 校验和的实际应用场景校验和Checksum的基本作用是通过某种哈希算法对数据计算出一个固定长度的摘要值用于验证数据在传输或存储过程中是否发生变化。应用场景非常广比如下载大文件时官网给出的MD5值、固件升级包里附带的SHA256校验串、后端接口日志里的数据完整性校验以及最简单的网络协议帧尾的CRC校验。我在实际工作中遇到的需求是每次发布嵌入式固件后需要把生成的.bin文件和对应的校验值一起打包发给测试人员。测试人员拿到文件后需要自己再算一遍校验值用来确认文件没有被邮件系统或者聊天工具偷偷改掉。之前他们一直用一个网上找来的绿色软件但那个软件只支持CRC32而且界面老旧在Win10高DPI下显示模糊。于是我决定自己写一个顺便练习一下MFC。1.2 MFC与VS2013的适配原因选择MFC而不是C# WinForms或者Qt原因有三个。第一目标测试机是Windows 7和Windows 10的32位/64位混合环境MFC程序编译出来体积小依赖少用VC运行库静态编译后基本拷贝即用不需要安装.NET Framework。第二我自己最熟悉的还是CMFC虽然老旧但资料多遇到问题随手能搜到解决方案。第三VS2013对MFC的支持已经非常成熟类向导和对话框编辑器都够用不需要额外配置复杂的开发环境。当然MFC的劣势也很明显控件样式复古高DPI适配需要自己处理字符串操作不如C#方便。但作为内部工具这些都不是致命问题。核心目标是快速实现、稳定运行、操作简单。2. 核心原理拆解校验和算法与MFC消息机制2.1 常见校验和算法对比写这个工具之前我先把市面上常见的校验算法整理了一遍方便选型。下面的表格是我个人总结的对比算法输出长度速度安全性典型用途CRC3232位极快极弱防随机错误网络包校验、压缩包校验MD5128位快弱有碰撞可能文件完整性校验、下载校验SHA-1160位中弱已被理论攻破旧版Git、部分协议SHA-256256位中强固件签名、安全校验SM3256位中强国密国内金融、政务系统对于我的需求测试人员主要是确认文件没被意外篡改不是对抗恶意攻击所以MD5足够用。但为了工具更通用我把CRC32也加了进去因为很多老固件工具只认CRC32。实现上MD5的代码量大一些CRC32则可以用查表法轻松搞定。2.2 MFC消息映射与按钮事件处理MFC的核心机制是消息映射它把Windows消息如鼠标点击、键盘输入映射到对应的成员函数。在这个工具里我需要处理三个关键消息打开文件按钮的点击消息、计算按钮的点击消息、拖拽文件的消息。消息映射在头文件里用DECLARE_MESSAGE_MAP()声明在源文件里用BEGIN_MESSAGE_MAP和END_MESSAGE_MAP定义。例如BEGIN_MESSAGE_MAP(CCheckSumDlg, CDialogEx) ON_BN_CLICKED(IDC_BTN_OPEN, CCheckSumDlg::OnBnClickedBtnOpen) ON_BN_CLICKED(IDC_BTN_CALC, CCheckSumDlg::OnBnClickedBtnCalc) END_MESSAGE_MAP()这里有个新手很容易犯的错IDC_BTN_OPEN和IDC_BTN_CALC必须在资源文件里真实存在而且ID不能重复。如果按钮ID写错编译不报错但运行时点击按钮毫无反应排查起来很浪费经历。我后来习惯在对话框编辑器里先放置好按钮再用类向导绑定事件最大限度地避免手写ID出错。3. 手把手实现从界面设计到算法集成3.1 创建MFC对话框工程打开VS2013新建项目选择“MFC应用程序”项目名称我起的是MFCApplicationCheckSum和压缩包名保持一致。在应用程序类型页面选择“基于对话框”然后一路默认直到完成。项目生成后会有一个主对话框默认ID是IDD_MFCAPPLICATIONCHECKSUM_DIALOG。这里建议把对话框的字体改成“微软雅黑 9号”否则在Win10下显示宋体小五号字非常吃力。修改方法在资源视图中双击对话框模板右键属性找到字体项修改即可。同时把对话框的Border属性保持为“对话框边框”否则拉伸窗口时控件跟不上。3.2 界面控件布局与变量绑定我的界面布局很简单一个静态文本框提示一个编辑框显示文件路径只读一个按钮“选择文件”一个小编辑框显示计算结果一个按钮“计算校验和”还有一个列表控件展示历史结果。为了美观我用了一个分组框把相关控件框起来。关键控件的ID和变量绑定如下控件类型控件ID变量类型作用编辑框路径IDC_EDIT_PATHCString显示选择的文件路径按钮选择IDC_BTN_OPEN无弹出文件选择框编辑框结果IDC_EDIT_RESULTCString显示校验结果按钮计算IDC_BTN_CALC无触发计算列表控件IDC_LIST_HISTORYCListCtrl显示历史记录变量绑定用类向导的“添加变量”功能编辑框绑定CString列表控件绑定CListCtrl。绑定后在代码里通过UpdateData(TRUE)和UpdateData(FALSE)来读写变量值。这个机制非常方便省去了大量手动操作控件的代码。3.3 文件选择与读取逻辑点击“选择文件”按钮后用CFileDialog打开文件选择对话框。这里有个细节CFileDialog的构造函数最后一个参数指向缓冲区大小默认值在读取长路径时可能溢出所以我传了OFN_FILEMUSTEXIST标志并设置lpstrFilter来过滤常见文件类型。void CCheckSumDlg::OnBnClickedBtnOpen() { CFileDialog dlg(TRUE, NULL, NULL, OFN_FILEMUSTEXIST | OFN_HIDEREADONLY, _T(所有文件 (*.*)|*.*||), this); if (dlg.DoModal() IDOK) { m_strFilePath dlg.GetPathName(); UpdateData(FALSE); } }文件读取是校验的基础。我一开始用CFile按块读取每块64KB循环读取到文件末尾。这样做的好处是内存占用可控不会因为大文件导致程序崩溃。代码大致如下CFile file; if (!file.Open(m_strFilePath, CFile::modeRead | CFile::shareDenyNone)) { AfxMessageBox(_T(文件打开失败)); return; } const UINT bufSize 65536; BYTE* buffer new BYTE[bufSize]; UINT bytesRead 0; while ((bytesRead file.Read(buffer, bufSize)) 0) { // 这里调用校验算法更新上下文 } delete[] buffer; file.Close();3.4 校验和计算与结果展示这里我同时实现了CRC32和MD5。CRC32用查表法预计算一个256项的表然后逐字节更新。MD5则按照RFC 1321实现维护四个链接变量每次处理64字节的块。为了让代码清晰我封装了两个类CCRC32和CMD5。它们的接口统一为Init()、Update(buffer, length)和Final(output)这样主对话框代码不用关心算法的内部实现。主对话框在计算按钮事件里先得到文件路径然后根据用户选择的算法类型用两个单选按钮区分创建对应的算法对象循环读取文件并更新最后把结果格式化成十六进制字符串显示在结果编辑框里同时插入到历史列表框中。void CCheckSumDlg::OnBnClickedBtnCalc() { UpdateData(TRUE); if (m_strFilePath.IsEmpty()) { AfxMessageBox(_T(请先选择文件)); return; } // 省略打开文件代码... CString strResult; if (m_bIsCRC32) // 用户选择了CRC32 { CCRC32 crc; crc.Init(); // 循环读文件并crc.Update(buffer, bytesRead); unsigned long value crc.Final(); strResult.Format(_T(CRC32: %08X), value); } else { CMD5 md5; md5.Init(); // 循环读文件并md5.Update(buffer, bytesRead); BYTE digest[16]; md5.Final(digest); // 将digest格式化为32位十六进制字符串 } m_strResult strResult; UpdateData(FALSE); // 插入历史列表框 }实际操作中还发现一个坑CFile::GetLength()返回的是ULONGLONG类型格式化时要用%I64u否则显示异常。另外文件路径包含中文时默认的CString与char*互相转换会乱码我统一用宽字符版本_T宏和CFile的宽字符重载来规避。4. 实测中的坑与优化方案4.1 文件读取的缓冲区问题第一次测试我读取一个1.2GB的虚拟机镜像文件程序直接卡死在我面前。排查后发现原因不在算法而在CFile::Read的调用方式。当读取循环使用同一块缓冲区时如果缓冲区大小不是算法块大小的倍数MD5的预处理逻辑会出问题。更严重的是我在每次循环里都调用了UpdateData(FALSE)刷新界面导致界面重绘阻塞了文件读取。解决办法是取消在循环中的任何界面操作只保留算法更新逻辑。同时把缓冲区大小从64KB调整到1MB减少系统调用次数。优化后1.2GB文件计算MD5大约耗时2.5秒CRC32大概1.8秒完全可接受。4.2 界面卡顿与异步处理如果文件较大计算过程中窗口会无响应拖动都没反应。这是因为所有操作都在UI线程执行。对于内部工具最简单的方案是在计算前禁用计算按钮并在状态栏显示“计算中...”计算完成后再恢复。这个方案虽然不优雅但实现成本最低已经能满足测试人员的需求。如果想更专业一点可以用AfxBeginThread启动一个工作线程通过自定义消息把结果传回UI线程。但MFC线程间通信容易引入bug尤其是我这种半路出家的MFC使用者为避免把线搞乱我选择“假异步”也就是处理Windows消息循环。具体做法是对话框类里增加一个布尔变量m_bCalculating在OnBnClickedBtnCalc里设置一个定时器每100毫秒处理一次消息但校验算法本身还是同步的只是让窗口不至于完全僵硬。4.3 大文件校验的优化策略大文件校验的优化核心是减少I/O次数和算法块对齐。我用内存映射文件CreateFileMapping来替代CFile::Read一次性把文件映射到进程地址空间。这样读取逻辑变成直接内存访问速度明显提升。但内存映射对超大文件4GB以上要小心32位进程地址空间不够用我目前的目标文件都在2GB以内实测没问题。另一个优化是CRC32表生成本身很耗时。我把查表法用的表改成静态数组在CCRC32类里用静态初始化避免每次创建对象时重新生成。MD5则没有这种优化需求因为它的每次更新都是固定计算量。5. 打包发布与使用心得5.1 MFC项目打包注意事项MFC程序发布时最头疼的是运行库依赖。VS2013支持静态链接MFC和C运行库在项目属性的“常规”里把“MFC的使用”改为“在静态库中使用MFC”“C - 代码生成”里的“运行库”改为“多线程调试/MTd”或“多线程/MT”。这样生成的.exe不需要随带mfc120.dll和msvcr120.dll在干净的Windows 7上也能直接跑。不过静态链接会导致exe体积从几十KB膨胀到几MB但换来的是部署方便值得。另外我还在exe同目录放了README.txt写清楚算法类型和压缩包内各文件用途。打包发布时我做了三件事一是把Debug改为Release编译二是用空白路径测试防止exe路径中有中文导致CFileDialog异常三是用UPX压缩壳把体积从2.8MB压到1.1MB。不过压缩壳可能会被杀毒软件误报内部工具无所谓如果对外发布建议不压缩。5.2 工具的整体评价与扩展思路这个工具从立项到可用大概花了我两个周末的时间。界面虽然简陋但测试人员反馈“比之前那个好用多了”。扩展空间也很大目前已经有人建议我在列表控件里加“复制”和“对比”功能我打算下一步加入。如果继续完善我想到的优化方向包括支持拖拽文件到窗口自动填充路径、增加SHA-256和SM3算法、增加多文件批量计算、把历史结果导出为CSV文件。这些功能在MFC里都不难实现核心还是消息映射和控件操作的熟练度。最后分享一个小小的心得MFC虽然老旧但它和Windows API的结合非常紧密遇到问题翻MSDN和Stack Overflow答案几乎是现成的。而且用MFC写内部工具最大的好处是不用折腾环境依赖一台装满VS2013的机器就能全流程搞定。如果你也在做类似的桌面小工具不妨试试这个路线。本文还有配套的精品资源点击获取
返回列表