简介本资源面向使用 Visual Studio 2022 与 Qt 进行 C 桌面开发的工程师聚焦于在 Qt 项目中集成 QXlsx 库以读写 xlsx 表格文件这一常见需求。资源包为 7z 压缩格式整体约 65.44MB内含工程源码、QXlsx 库文件及配套依赖主要文件类型涵盖 C 源文件、头文件与项目配置便于直接编译运行与二次开发。描述中特别给出了 Qt 5.14.2 msvc2017_64 环境下需要补充的包含目录涉及 QtGui、QtCore 及其 private 私有头文件路径这正是 QXlsx 编译时常被忽略的关键配置能帮助读者快速排除头文件找不到的编译错误。目前已有 638 人学习下载适合正在做数据导入导出、报表生成或表格处理功能的 Qt 开发者参考可借此掌握第三方库在 VSQt 工程中的目录配置与调用方式节省自行摸索的时间。1. 为什么我劝你先别急着在 VS2022 里点“安装 Qt 插件”如果你正在 Windows 上做桌面工具又不想用 QML 那套大概率会走到「VS2022 Qt QXlsx」这条路上。这套组合解决的是一个很具体的问题用 C 写带界面的工具同时把 Excel 当数据交换格式——不是导出 CSV 那种凑合方案而是真正读写 .xlsx保留单元格格式、公式和多个 sheet。适合谁做内部工具、测试上位机、数据批处理面板的开发者尤其是团队里已经用 VS 做 C 项目、不想再装一套 Qt Creator 的人。但这里有个反直觉的结论VS2022 装 Qt 插件这一步反而是整条链路里最不容易翻车的一环。真正让人卡住的是 QXlsx 的编译方式、字符编码和运行时 DLL 的部署。我见过太多人插件装完、界面跑起来结果一调 QXlsx 就报 LNK2019 或者运行时直接崩。这篇就把这条链路从头拆一遍重点放在能复现的步骤和参数上。2. 环境搭建VS2022 与 Qt 的版本咬合关系2.1 版本选型不是越新越好VS2022 是 64 位 IDE但它能编译 32 位和 64 位目标。Qt 这边你得先确定用哪套编译器。Qt 官方为 Windows 提供两种预编译包MSVC 和 MinGW。既然你选了 VS2022就必须用 MSVC 版MinGW 版和 VS 的 ABI 不兼容混用会在链接期炸出一堆找不到符号的错误。具体版本上Qt 5.15 和 Qt 6.x 对 VS2022 的支持有差异。Qt 5.15 的官方预编译包默认用 MSVC2019 编译但 MSVC2019 和 MSVC2022 的 ABI 是兼容的所以能在 VS2022 里直接用。Qt 6.x 则从 6.2 开始明确支持 MSVC2022。我一般会选 Qt 5.15.2 配 VS2022原因是 QXlsx 在 Qt 5 下的资料最多踩坑成本低。如果你非要用 Qt 6注意 QXlsx 的某些版本对 Qt 6 的 API 变更还没完全跟上得挑较新的 commit。安装时有个细节Qt 在线安装器里MSVC 套件是分版本的比如msvc2019 64-bit、msvc2022 64-bit。你装哪个后面在 VS 里配的 Qt 版本就要对应哪个。别装完msvc2019却在 VS 里指到msvc2022的路径那套头文件和库文件对不上。2.2 在 VS2022 里挂接 Qt 的两种方式第一种是装「Qt Visual Studio Tools」扩展。VS2022 菜单栏 → 扩展 → 管理扩展 → 搜索 Qt → 下载安装重启 VS。然后在「扩展 → Qt VS Tools → Qt Versions」里添加你的 Qt 安装路径比如D:\Qt\5.15.2\msvc2019_64。添加后给它起个名字比如Qt5.15.2_msvc2019_64。第二种是不装扩展手动配包含目录和库目录。这种方式更透明适合你想搞清楚每个路径到底在干什么。手动配的话在项目属性里要设这几项配置项典型值C/C → 常规 → 附加包含目录D:\Qt\5.15.2\msvc2019_64\include及各子模块链接器 → 常规 → 附加库目录D:\Qt\5.15.2\msvc2019_64\lib链接器 → 输入 → 附加依赖项Qt5Core.lib、Qt5Gui.lib、Qt5Widgets.lib等C/C → 代码生成 → 运行库必须与 Qt 预编译包一致通常是/MD或/MDd这里最容易翻车的是运行库选项。Qt 官方预编译包用的是/MDRelease和/MDdDebug如果你的项目设成/MT链接时会报一堆LNK2038运行库不匹配。改法就是在项目属性里把「运行库」改成「多线程 DLL (/MD)」或「多线程调试 DLL (/MDd)」。提示手动配路径时Qt 的 include 目录下每个模块是独立子目录比如QtCore、QtGui。你写#include QApplication能编过是因为 Qt 的头文件里做了转发但链接时还是得把对应的 .lib 加上。2.3 验证 Qt 环境是否真的通了别急着写业务代码先建一个空的 Qt Widgets 项目只放一个QApplication和一个空窗口编译运行。这一步的目的是确认编译器、链接器、运行库三者对齐。如果这个空窗口能弹出来说明 Qt 环境没问题后面出问题就只可能是 QXlsx 或你的代码。#include QApplication #include QWidget int main(int argc, char *argv[]) { QApplication app(argc, argv); QWidget w; w.setWindowTitle(Qt env check); w.resize(320, 200); w.show(); return app.exec(); }这段代码里QApplication负责初始化 Qt 的事件循环和全局状态QWidget是最基础的窗口类。resize设的是客户区大小不含标题栏。如果编译时报无法解析的外部符号八成是.lib没加全或者运行库不匹配如果编译过了但运行时报缺 DLL那是PATH里没有 Qt 的bin目录。3. QXlsx 的接入源码编译还是直接引库3.1 QXlsx 是什么为什么不用 COM 调 ExcelQXlsx 是一个纯 C 的 Qt 库用来读写 .xlsx 文件。它不依赖 Excel 安装也不走 COM 接口所以能在没有 Office 的机器上跑。这一点对部署很关键——你不可能要求每台客户机都装 Office。相比之下用QAxObject调 Excel COM 的方案虽然功能全但部署时依赖 Office而且进程外调用慢、容易卡死。QXlsx 的定位是「够用」读写单元格值、公式、格式、合并单元格、多个 sheet、图表有限支持。它不适合做复杂的 Excel 报表引擎但做数据导入导出、配置表读写绰绰有余。3.2 把 QXlsx 源码拉进 VS 项目的正确姿势QXlsx 官方推荐用 qmake 编译成库但在 VS2022 里我一般直接把源码文件加进项目省去编译库和配库路径的麻烦。具体做法从仓库把QXlsx的source目录整个拷到你的项目目录下比如third_party/QXlsx。在 VS 里右键项目 → 添加 → 现有项把source下所有.cpp和.h加进来。注意source下还有子目录比如xlsx里面的文件也要加。在项目属性 → C/C → 附加包含目录里加上third_party/QXlsx/header和third_party/QXlsx/source。这样做的代价是编译时间变长但好处是版本可控、调试能直接跟进去。如果你项目多可以单独建一个静态库项目把 QXlsx 编成.lib然后其他项目引用。静态库项目的运行库设置要和主项目一致否则一样会LNK2038。#include xlsxdocument.h #include xlsxformat.h void writeDemo() { QXlsx::Document xlsx; xlsx.write(A1, 名称); xlsx.write(B1, 数量); xlsx.write(A2, 零件A); xlsx.write(B2, 120); QXlsx::Format fmt; fmt.setFontBold(true); fmt.setFillPattern(QXlsx::Format::PatternSolid); fmt.setPatternBackgroundColor(QColor(#D9E1F2)); xlsx.setCellFormat(1, 1, fmt); xlsx.setCellFormat(1, 2, fmt); xlsx.saveAs(demo.xlsx); }这段代码里Document构造时如果不传路径就是新建一个空工作簿。write的第一个参数是单元格地址支持A1这种写法也支持(row, col)重载。Format用来设格式setFontBold是加粗setFillPattern配setPatternBackgroundColor是设背景色。setCellFormat的行列从 1 开始计数不是 0。saveAs会覆盖同名文件没有确认提示。3.3 读 Excel 时的类型陷阱读的时候read返回的是QVariant。单元格里是数字还是文本取决于 Excel 里存的类型。如果你写进去的是120读出来是int或double如果写的是120读出来就是QString。这个区别在批量处理时很要命。void readDemo(const QString path) { QXlsx::Document xlsx(path); if (!xlsx.load()) { qWarning() load failed; return; } QXlsx::CellRange range xlsx.dimension(); for (int row range.firstRow(); row range.lastRow(); row) { for (int col range.firstColumn(); col range.lastColumn(); col) { QVariant v xlsx.read(row, col); if (v.isNull()) { continue; } if (v.typeId() QMetaType::QString) { QString s v.toString().trimmed(); // 处理文本 } else if (v.canConvertdouble()) { double d v.toDouble(); // 处理数值 } } } }dimension()返回的是有数据的区域不是整个 sheet 的最大行列。read对空单元格返回空QVariant用isNull判断。typeId比type()更直接Qt 6 里type()已经废弃了。trimmed是为了去掉 Excel 里常见的尾部空格这个坑我踩过——从 Excel 复制粘贴的数据末尾经常带不可见空格直接比较字符串会不相等。注意QXlsx 读公式单元格时默认返回的是公式计算结果不是公式本身。如果你需要公式文本得用cellAt拿Cell对象再取formula()。这个行为在文档里没写得很显眼但实际用的时候经常需要。4. 避坑与排查那些让我加班到凌晨的报错4.1 LNK2019 找不到 QXlsx 符号现象编译通过链接时报无法解析的外部符号 public: __cdecl QXlsx::Document::Document(void)。原因QXlsx 的.cpp文件没全部加进项目或者加进来了但没参与编译。常见的是只加了header没加source或者source/xlsx子目录下的文件漏了。解决在 VS 的解决方案资源管理器里展开 QXlsx 的筛选器确认每个.cpp都在并且右键 → 属性 → 常规 → 项类型是「C/C 编译器」。如果是从资源管理器拖进来的有时会被识别成「不参与生成」手动改一下。4.2 运行时崩溃在 QXlsx::Document 构造现象程序启动后一new QXlsx::Document就崩调用栈停在QZipReader或QBuffer相关的地方。原因Qt 的QtGui模块没链接或者链接了但 DLL 没部署。QXlsx 内部用了QImage和QColor这些在Qt5Gui.dll里。如果你只加了Qt5Core.lib链接能过因为 QXlsx 的 .lib 里已经引了但运行时找不到Qt5Gui.dll就崩。解决在项目属性 → 链接器 → 输入里补上Qt5Gui.lib并且把D:\Qt\5.15.2\msvc2019_64\bin加到系统PATH或者把需要的 DLL 拷到 exe 同目录。用windeployqt工具可以自动拷命令是windeployqt your.exe --no-translations。4.3 中文乱码写进去是问号读出来是乱码现象xlsx.write(A1, 中文)之后打开 Excel 看到的是???或者乱码。原因源码文件的编码和 Qt 的字符串转换没对齐。VS2022 默认可能用 GBK 存.cpp文件而 Qt 内部按 UTF-8 处理。QString从const char*构造时如果没指定编码Qt 5 会按QTextCodec::codecForLocale()来中文 Windows 上就是 GBK但 QXlsx 写文件时按 UTF-8 写两边不一致。解决在main函数开头加QTextCodec::setCodecForLocale(QTextCodec::codecForName(UTF-8));并且把源码文件另存为 UTF-8 with BOM。或者更彻底所有字符串用QStringLiteral(中文)包起来QStringLiteral在编译期就按 UTF-16 处理不经过运行时编码转换。4.4 保存大文件时内存暴涨现象写几万行数据时内存占用一路涨到几个 GB最后bad_alloc。原因QXlsx 的Document把所有单元格数据都放在内存里的QMap里写的时候才序列化。数据量大时内存占用是数据量的好几倍。解决分批写。每写 5000 行就saveAs一次然后重新构造Document。或者改用流式写入的方式但 QXlsx 对流式支持有限。如果数据量真的很大考虑直接写 CSV 或者用其他库。这个坑没有优雅的解法只能控制单次写入的量。4.5 Debug 能跑 Release 崩现象Debug 配置下一切正常切到 Release 就崩在 QXlsx 内部。原因运行库不匹配。Debug 用/MDdRelease 用/MD但 QXlsx 的源码如果是在 Debug 下编的切 Release 时没重新编译或者项目属性里 QXlsx 相关文件的运行库设置没跟着变。解决切配置后对 QXlsx 的所有.cpp文件执行「重新编译」。更稳的做法是把 QXlsx 单独建一个静态库项目Debug 和 Release 各编一份主项目按配置引用对应的.lib。5. 进阶把 QXlsx 封装成配置表读写器5.1 为什么要在 QXlsx 之上再包一层直接用 QXlsx 的 API 写业务代码会有两个问题一是行列号硬编码改表结构就要改代码二是类型转换散落各处读一个int要写三行判断。我一般会包一个ConfigTable类用表头名做键内部维护列名到列号的映射。class ConfigTable { public: bool load(const QString path, const QString sheetName QString()) { m_doc.reset(new QXlsx::Document(path)); if (!m_doc-load()) { return false; } if (!sheetName.isEmpty()) { m_doc-selectSheet(sheetName); } m_headerRow 1; buildColumnMap(); return true; } QVariant value(int row, const QString colName) const { auto it m_colMap.find(colName); if (it m_colMap.end()) { return {}; } return m_doc-read(row, it.value()); } int rowCount() const { return m_doc-dimension().lastRow(); } private: void buildColumnMap() { m_colMap.clear(); QXlsx::CellRange range m_doc-dimension(); for (int col range.firstColumn(); col range.lastColumn(); col) { QVariant v m_doc-read(m_headerRow, col); if (v.isValid()) { m_colMap.insert(v.toString().trimmed(), col); } } } QScopedPointerQXlsx::Document m_doc; QMapQString, int m_colMap; int m_headerRow 1; };这个类的核心是buildColumnMap读第一行作为表头建立「列名 → 列号」的映射。之后业务代码用value(row, 数量)就能取值不用关心它在第几列。selectSheet用来切到指定 sheet不传就用默认的第一个。QScopedPointer管理Document的生命周期避免手动 delete。5.2 写回时的格式保留技巧读进来再写回去最容易丢的是格式。QXlsx 的Document在load之后单元格的格式信息是保留的但如果你用write覆盖了某个单元格它的格式会被重置。要保留原格式得先读cellAt拿Format写的时候再设回去。void updateCell(QXlsx::Document doc, int row, int col, const QVariant newVal) { QXlsx::Cell *cell doc.cellAt(row, col); QXlsx::Format fmt; if (cell) { fmt cell-format(); } doc.write(row, col, newVal); if (cell) { doc.setCellFormat(row, col, fmt); } }cellAt返回的是指针如果单元格不存在就返回nullptr。format()拿到的Format对象可以直接复用。setCellFormat要在write之后调用否则会被write重置。这个顺序不能反。5.3 验证写出的文件是否真的正确别只用 Excel 打开看一眼就完事。Excel 对格式的容错很高有些问题它不报错但别的工具读会出问题。我一般会写一个校验函数用 QXlsx 自己再读一遍检查行数、列数、关键单元格的值是否和预期一致。bool verify(const QString path, int expectRows, int expectCols) { QXlsx::Document doc(path); if (!doc.load()) { return false; } QXlsx::CellRange range doc.dimension(); if (range.lastRow() ! expectRows || range.lastColumn() ! expectCols) { qWarning() dimension mismatch: range.lastRow() range.lastColumn(); return false; } return true; }这个函数只做了最基本的维度校验实际项目里还会检查特定单元格的值和类型。关键点是用同一个库读自己写的文件能发现大部分序列化问题。如果连自己都读不回来那肯定是写的时候就有问题。从那以后我每次接入新的 Excel 读写库都会先写一个「写出去再读回来」的往返测试确认数据不丢、类型不变、格式不崩再往业务代码里集成。这个习惯帮我省掉了至少三次上线后的紧急修复。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?