基于MFC的Gerber文件读取器:快速解析与显示PCB线路数据

发布时间:2026/10/20 5:09:13
基于MFC的Gerber文件读取器:快速解析与显示PCB线路数据 简介这份资源面向使用 Visual Studio 与 MFC 进行桌面开发的 C 学习者聚焦 Gerber 文件解析与界面展示这一典型 PCB 场景。Gerber 是印制电路板制造中常用的图形描述格式程序通过解析其指令与坐标数据将内容逐行呈现在 List 控件中便于设计人员检查与验证。资源包共 25 个文件以 h 头文件与 cpp 源文件为主另含 vcxproj、sln 等工程配置及 rc 资源脚本、ico 图标与 ReadMe 说明压缩包约 150KB结构完整可直接编译运行。目前已有 801 人学习下载。读者可从中掌握 Gerber 格式解析思路、CListCtrl 数据填充方式、CFileDialog 文件选择流程以及 MFC 消息处理与异常保护写法是提升 C 与 PCB 领域实践能力的一份参考范例。1. 从一堆 Gerber 文件里把线路“读”出来这个 MFC 小工具到底能干什么如果你在 PCB 行业待过一定遇到过这种场景板厂或者客户甩过来一个压缩包里面躺着几十个 Gerber 文件后缀五花八门——.gbr、.art、.ger、.gtl、.gbl还有钻孔文件.drl。你想快速确认某一层到底画了什么用 CAM 软件打开吧启动慢、操作重用文本编辑器直接看吧满屏都是G04、%FSLAX46Y46*%、D10*这种指令眼睛都要看花。这个项目就是冲着这个痛点来的用 VS MFC VC 写一个轻量级 Gerber 文件读取器把文件内容按行解析出来分行显示在界面上让你不用开重型软件就能快速浏览线路数据。它适合两类人一类是刚接触 PCB 制造、想搞懂 Gerber 格式到底长什么样的新手拿它当学习工具一行行看指令怎么组织另一类是做 CAM 工具链、需要在自己的 MFC 工程里嵌入 Gerber 解析能力的开发者可以直接把核心解析逻辑抠出来复用。整个项目基于 MFC 对话框框架用 C 写编译出来就是一个 exe双击就能跑不依赖任何第三方库。下面我从工程结构、核心解析代码、界面交互到二次开发把这个项目拆开讲一遍。2. 工程结构与技术选型为什么用 MFC 而不是 Qt2.1 项目目录长什么样拿到源码后第一件事是看目录结构。这个项目是典型的 VS MFC 对话框工程目录大致如下GerberReader/ ├── GerberReader.sln # VS 解决方案文件 ├── GerberReader/ │ ├── GerberReader.vcxproj # 工程文件 │ ├── GerberReader.h # 主对话框类声明 │ ├── GerberReader.cpp # 主对话框类实现 │ ├── GerberReaderDlg.h # 对话框头文件 │ ├── GerberReaderDlg.cpp # 对话框实现核心逻辑在这里 │ ├── GerberParser.h # Gerber 解析类声明 │ ├── GerberParser.cpp # Gerber 解析类实现 │ ├── resource.h # 资源 ID 定义 │ ├── GerberReader.rc # 资源脚本包含对话框布局 │ └── res/ # 图标等资源 └── README.md核心就三个文件GerberReaderDlg.cpp负责界面和文件读取GerberParser.cpp负责把文件内容拆成一行行可读的指令resource.h管控件 ID。没有多余的依赖干净利落。2.2 为什么选 MFC 而不是 Qt这个问题我被问过很多次。Qt 跨平台、界面漂亮但在这个场景下 MFC 反而更合适。原因有三第一目标用户是 Windows 桌面端的 PCB 工程师MFC 编译出来的 exe 体积小、启动快不需要带一堆 Qt DLL第二很多老牌 CAM 软件本身就是 MFC 写的如果要做插件或者嵌入现有系统MFC 的兼容性更好第三这个工具的功能很单一——读文件、分行显示用 MFC 的CListBox或者CRichEditCtrl就能搞定没必要上 Qt 那套信号槽。当然MFC 的坑也不少后面避坑章节会细说。这里先记住一点如果你要二次开发建议保持 MFC 框架不动只改解析逻辑和显示控件这样最省事。2.3 对话框布局与控件说明打开GerberReader.rc能看到主对话框上摆了这几个控件控件 ID类型作用IDC_BTN_OPENButton触发文件选择对话框IDC_EDIT_PATHEdit Box显示当前打开的文件路径IDC_LIST_LINESListBox分行显示 Gerber 文件内容IDC_STATIC_COUNTStatic Text显示总行数IDC_BTN_CLEARButton清空列表布局很简单左边一个按钮加路径框下面一个大 ListBox 占满剩余空间。这种布局的好处是窗口可以自由拉伸ListBox 会自动调整大小。如果你要加功能比如搜索、过滤、高亮可以在右侧或者底部再加控件。3. 核心解析逻辑Gerber 文件到底怎么读3.1 Gerber 文件的基本结构在写代码之前得先搞清楚 Gerber 文件长什么样。Gerber 格式是 PCB 行业的标准光绘格式本质上是一系列指令的集合。一个典型的 Gerber 文件开头是这样的G04 This is a comment* %FSLAX46Y46*% %MOMM*% %ADD10C,0.150000*% D10* X100000Y100000D02* X150000Y100000D01* M02*每一行以*结尾表示指令结束%包裹的是扩展指令G04是注释D01是画线D02是移动D03是闪绘。我们的目标不是解析这些指令的几何含义而是先把它们按行拆出来让用户能看到原始内容。所以解析逻辑的核心就是按行读取、去掉行尾的*、识别指令类型、分行显示。3.2 文件读取与分行处理先看GerberParser.cpp里的读取函数// GerberParser.cpp #include pch.h #include GerberParser.h #include fstream #include sstream // 读取 Gerber 文件返回按行分割的字符串数组 std::vectorCString CGerberParser::ReadGerberFile(const CString filePath) { std::vectorCString lines; std::ifstream file(filePath, std::ios::in | std::ios::binary); if (!file.is_open()) { return lines; // 文件打不开返回空数组 } std::string rawLine; while (std::getline(file, rawLine)) { // 去掉行尾的 \rWindows 换行符 if (!rawLine.empty() rawLine.back() \r) { rawLine.pop_back(); } // 跳过空行 if (rawLine.empty()) { continue; } // 转成 CString 方便 MFC 显示 CString line(rawLine.c_str()); lines.push_back(line); } file.close(); return lines; }这段代码有几个关键点。第一用std::ifstream以二进制模式打开文件避免 Windows 下文本模式对换行符的自动转换干扰。第二std::getline按\n分割但 Windows 文件行尾是\r\n所以手动去掉\r。第三空行直接跳过因为 Gerber 文件里空行没有意义显示出来反而占地方。第四返回std::vectorCString而不是std::vectorstd::string是为了后续直接往 MFC 控件里塞省一次转换。参数说明filePath是文件的完整路径支持中文路径因为用了std::ifstream的宽字符版本需要额外处理这里简化了实际项目中建议用_wfopen或者CFile。返回值是行内容的数组如果文件打不开就返回空数组调用方需要判断。3.3 指令分类与格式化显示光把行读出来还不够Gerber 文件里有些行是注释有些是参数设置有些是坐标数据。为了显示更清晰可以给每行加个前缀标记// 根据行内容判断指令类型返回带标记的显示字符串 CString CGerberParser::FormatLine(const CString rawLine) { CString formatted; CString trimmed rawLine; trimmed.Trim(); // 去掉首尾空格 if (trimmed.IsEmpty()) { return _T(); } // G04 开头的是注释 if (trimmed.Left(3) _T(G04)) { formatted.Format(_T([注释] %s), trimmed.GetString()); } // % 开头的是扩展指令 else if (trimmed[0] _T(%)) { formatted.Format(_T([参数] %s), trimmed.GetString()); } // D 开头的是绘图指令 else if (trimmed[0] _T(D) trimmed.GetLength() 1 _istdigit(trimmed[1])) { formatted.Format(_T([绘图] %s), trimmed.GetString()); } // X/Y 开头的是坐标数据 else if (trimmed[0] _T(X) || trimmed[0] _T(Y)) { formatted.Format(_T([坐标] %s), trimmed.GetString()); } // 其他归为未知指令 else { formatted.Format(_T([其他] %s), trimmed.GetString()); } return formatted; }这段代码的逻辑很直白根据行首字符判断指令类型加上[注释]、[参数]、[绘图]、[坐标]这样的前缀。这样用户在 ListBox 里一眼就能看出哪行是注释、哪行是实际数据。Trim()去掉首尾空格避免因为缩进导致判断失误。_istdigit是 MFC 提供的字符判断函数比手动比较0到9更简洁。注意Gerber 规范里指令类型远不止这几种比如M02是文件结束G01是线性插值模式。这里只做了最粗的分类目的是让显示更友好不是做完整的语法解析。如果你要二次开发做真正的 CAM 处理需要引入更完整的解析器。3.4 把解析结果塞进 ListBox解析完了接下来是显示。在GerberReaderDlg.cpp里打开文件按钮的响应函数大概是这样// GerberReaderDlg.cpp void CGerberReaderDlg::OnBnClickedBtnOpen() { CFileDialog dlg(TRUE, _T(gbr), NULL, OFN_HIDEREADONLY | OFN_FILEMUSTEXIST, _T(Gerber Files (*.gbr;*.art;*.ger)|*.gbr;*.art;*.ger|All Files (*.*)|*.*||), this); if (dlg.DoModal() ! IDOK) { return; } CString filePath dlg.GetPathName(); SetDlgItemText(IDC_EDIT_PATH, filePath); // 清空旧数据 CListBox* pList (CListBox*)GetDlgItem(IDC_LIST_LINES); pList-ResetContent(); // 解析文件 CGerberParser parser; std::vectorCString lines parser.ReadGerberFile(filePath); // 逐行格式化并添加到 ListBox for (size_t i 0; i lines.size(); i) { CString formatted parser.FormatLine(lines[i]); if (!formatted.IsEmpty()) { pList-AddString(formatted); } } // 更新总行数显示 CString countText; countText.Format(_T(总行数: %d), (int)lines.size()); SetDlgItemText(IDC_STATIC_COUNT, countText); }这段代码的流程是弹出文件选择对话框拿到路径后清空 ListBox调用解析器读文件逐行格式化后AddString到列表里最后更新行数统计。CFileDialog的过滤器设置成只显示 Gerber 常见后缀避免用户选错文件。ResetContent清空列表比逐条删除效率高。参数说明CFileDialog第一个参数TRUE表示打开文件对话框第二个参数是默认扩展名第三个是初始文件名第四个是标志位第五个是过滤器字符串。GetPathName返回用户选择的完整路径。AddString每次添加一行如果行数特别多比如几万行可以考虑用SetRedraw(FALSE)暂停重绘添加完再SetRedraw(TRUE)和Invalidate()避免界面卡顿。4. 避坑与常见问题那些年我踩过的 MFC 坑4.1 中文路径读不出来现象文件路径里有中文std::ifstream打开失败返回空数组。原因std::ifstream的构造函数接受const char*在 Windows 下默认用 ANSI 编码中文路径会乱码。解决改用CFile或者_wfopen或者把CString转成std::wstring再用宽字符版本的std::ifstream。我一般直接用 MFC 的CFile省心CFile file; if (!file.Open(filePath, CFile::modeRead)) { return lines; }4.2 ListBox 添加大量数据时界面卡死现象打开一个几万行的 Gerber 文件界面直接无响应要等好几秒才恢复。原因每次AddString都会触发一次重绘几万次重绘累积起来非常慢。解决添加前pList-SetRedraw(FALSE)添加完pList-SetRedraw(TRUE)再pList-Invalidate()。如果数据量特别大建议改用CListCtrl的虚拟列表模式只渲染可见区域。4.3 换行符处理不干净导致多出空行现象ListBox 里每隔一行就有一个空行。原因Gerber 文件在 Windows 下是\r\nstd::getline按\n分割后每行末尾还留着\r显示时\r被当成回车导致看起来像空行。解决读取后手动去掉\r就是前面代码里rawLine.pop_back()那一步。如果文件来自 Linux 系统只有\n这步也不会出错。4.4 文件编码问题导致乱码现象Gerber 文件里有中文注释显示出来是乱码。原因Gerber 文件可能是 UTF-8 编码而 MFC 默认用 ANSI 显示。解决读取时检测 BOM如果是 UTF-8 就转成CString的 Unicode 版本。简单做法是用MultiByteToWideChar转换int len MultiByteToWideChar(CP_UTF8, 0, rawLine.c_str(), -1, NULL, 0); wchar_t* buf new wchar_t[len]; MultiByteToWideChar(CP_UTF8, 0, rawLine.c_str(), -1, buf, len); CString line(buf); delete[] buf;4.5 对话框拉伸后控件不跟着变现象拉大窗口ListBox 还是原来大小右边留一大片空白。原因MFC 对话框默认不处理WM_SIZE消息控件位置固定。解决在对话框类里重写OnSize手动计算控件新位置void CGerberReaderDlg::OnSize(UINT nType, int cx, int cy) { CDialogEx::OnSize(nType, cx, cy); if (m_listLines.GetSafeHwnd()) { m_listLines.MoveWindow(10, 50, cx - 20, cy - 60); } }注意要在OnInitDialog里把控件绑定到成员变量否则GetSafeHwnd返回空。5. 二次开发与改造思路从“能看”到“能用”5.1 加一个搜索框快速定位指令现在只能一行行翻文件大了很痛苦。加个搜索框是最实用的改造。在对话框上放一个Edit Box和一个Button点击按钮时遍历 ListBox 内容找到匹配的行并选中void CGerberReaderDlg::OnBnClickedBtnSearch() { CString keyword; GetDlgItemText(IDC_EDIT_SEARCH, keyword); if (keyword.IsEmpty()) return; CListBox* pList (CListBox*)GetDlgItem(IDC_LIST_LINES); int count pList-GetCount(); for (int i 0; i count; i) { CString text; pList-GetText(i, text); if (text.Find(keyword) ! -1) { pList-SetCurSel(i); break; } } }这个逻辑是找到第一个匹配就停如果要找下一个可以记录上次位置继续往下搜。Find是CString的成员函数返回子串位置找不到返回 -1。5.2 把解析结果导出成 CSV有时候需要把 Gerber 内容导出来做分析。加一个导出按钮把 ListBox 内容写成 CSV 文件void CGerberReaderDlg::OnBnClickedBtnExport() { CFileDialog dlg(FALSE, _T(csv), _T(output.csv), OFN_OVERWRITEPROMPT, _T(CSV Files (*.csv)|*.csv||), this); if (dlg.DoModal() ! IDOK) return; CStdioFile file; if (!file.Open(dlg.GetPathName(), CFile::modeCreate | CFile::modeWrite)) { return; } CListBox* pList (CListBox*)GetDlgItem(IDC_LIST_LINES); int count pList-GetCount(); for (int i 0; i count; i) { CString text; pList-GetText(i, text); text.Replace(_T(,), _T(;)); // 避免逗号冲突 file.WriteString(text _T(\n)); } file.Close(); }CStdioFile是 MFC 封装的文件类WriteString直接写一行。注意把内容里的逗号替换掉否则 CSV 列会错位。5.3 用 CListCtrl 替代 ListBox 支持多列ListBox 只能显示一列如果想同时显示行号、指令类型、原始内容就得换CListCtrl。改造步骤把资源里的 ListBox 换成 ListCtrl在OnInitDialog里插入三列m_listCtrl.InsertColumn(0, _T(行号), LVCFMT_LEFT, 60); m_listCtrl.InsertColumn(1, _T(类型), LVCFMT_LEFT, 80); m_listCtrl.InsertColumn(2, _T(内容), LVCFMT_LEFT, 600);然后添加数据时用InsertItem和SetItemText。这样显示更结构化也方便排序和过滤。5.4 验证改造是否成功改完之后怎么确认没改坏我的习惯是准备三个测试文件一个只有几行的最小 Gerber 文件一个几千行的正常文件一个包含中文注释和特殊字符的边界文件。每次改完都跑一遍这三个文件看行数对不对、显示有没有乱码、搜索能不能命中。特别是中文路径和 UTF-8 编码这两个点最容易翻车。从那以后我每次改 MFC 的文件读取逻辑都强制走一遍“中文路径 大文件 特殊字符”这三件套省得后面返工。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询