C++读写Excel文件:xlnt库在VS2015下的配置与实战 📅 发布时间:2026/9/9 23:50:31 👁 浏览次数: 简介这是一份基于VS2015的xlnt库演示工程面向需要在C项目中读写Excel文件的开发者。xlnt是开源的跨平台C库无需COM组件即可生成或解析xlsx文件同时支持单元格样式、公式计算和图表创建等能力。压缩包共99个文件整体约3.21MB其中68个hpp头文件提供了xlnt核心接口定义dll与lib用于运行时和静态链接cpp、vcxproj及sln构成VS2015可直接打开的工程配置另附带xlsx样例文件和可运行的exe、pdb便于快速验证效果。演示代码从新建工作簿入手依次展示打开已有表格、定位活动工作表、按行列索引写入和读取数据、调整字体对齐以及保存输出文件等操作几乎覆盖日常Excel处理所需的主要API工程目录将库源码、代码示例与构建产出分层放置便于按需查阅与二次开发。目前已有4255人学习下载适合想摆脱Office COM依赖、在C端独立处理Excel数据的开发者参考。1. 项目内容整体设计与技术选型解析先说结论这个xlnt_demo项目要解决的事情其实非常朴素——在一个 VS2015 工程里用 C 代码去读写.xlsx格式的 Excel 文件且不希望引入重量级的 COM 组件、Office 安装依赖或者商业授权的库。xlnt 正好是这个场景下最顺手的选项之一它是一套头文件加源码即可集成的开源 C 库对 C11/14 支持得不错API 设计上也比较贴近现代 C 的使用习惯不用像传统 C 库那样手工管理一堆句柄和内存释放。1.1 为什么选 xlnt 而不是其他方案我在选型时其实对比了好几条路线这里把关键差异列出来方便你判断自己的项目适不适合走同一条路。方案优点缺点适合场景xlnt纯 C、开源、API 友好、跨平台只支持 xlsx不支持老版 xls桌面工具、内部系统、跨平台项目libxl功能全、性能好、支持 xls/xlsx商业授权、贵有预算的商业项目xlsxwriter写入性能极强只写不读批量导出报表直接调 Excel COM功能完整强大依赖安装 Office、慢、跨平台不可用Windows 平台小规模自动化手写 XML zip无第三方依赖工作量巨大、格式稍有偏差就损坏极简场景或学习研究xlnt 对xlsx的支持是完整的包括单元格类型、公式、样式、合并单元格、图表读取等日常办公自动化里的需求基本都覆盖住了。它在 GitHub 上持续维护社区活跃度不错遇到问题也能搜到一些讨论。它的构建方式很友好你可以用 vcpkg 安装也可以直接把源码编进自己的工程后一种方式对 VS2015 用户特别实用。1.2 xlsx 到底是什么在开写代码之前我一直建议先搞清楚 xlsx 文件的真实结构。其实 xlsx 就是一个 zip 压缩包里面装着一堆 XML 文件。我记得第一次拆开一个 xlsx 文件时还是挺惊讶的——xl/worksheets/sheet1.xml存着单元格数据xl/workbook.xml描述工作簿结构xl/styles.xml是样式定义等等。理解这一点最大的好处是遇到 xlnt 读不了或者写出来的文件打不开的情况你可以直接把后缀改成.zip解压开用文本编辑器检查 XML 里的内容问题基本一眼就能看出来。这套排查思路后来帮我节省了大量时间。xlnt 做的事情本质上是把这些 XML 的读写、压缩解压、数据类型转换全部封装成了一套类对象的操作接口。你不用关心文档规范调worksheet.cell(A1).value(你好)它就会帮你处理好 XML 的写入和压缩打包。这个抽象做得比较干净这也是我推荐它在 C 生态里作为 Excel 处理首选库的原因。2. VS2015 环境下编译与工程配置实操很多人在编译阶段就被卡住了所以我把整个工程建立和配置的过程单独捋一遍。我的环境是 Windows 10 Visual Studio 2015下面的步骤是基于这个组合来的。2.1 获取 xlnt 源码推荐直接从 GitHub 克隆官方仓库版本选择上不要盲目跟最新我实际测试下来1.5.0这个版本在 VS2015 下比较稳。新版对编译器版本的要求会稍微高一些VS2015 的 C14 支持不是特别完美老版本反而兼容性更好。获取源码有两种常用方式# 方式一Git 克隆指定标签 git clone --branch v1.5.0 --depth 1 https://github.com/tfussell/xlnt.git # 方式二直接下载 zip 包解压 # 在 GitHub releases 页面找 v1.5.0 的 Source code zip如果你的机器上 GitHub 访问不稳定可以考虑用 gitee 上的一些镜像仓库搜 xlnt gitee 就能找到。下载完确认目录里包含include/xlnt和source两个核心目录即可。2.2 把 xlnt 编进 VS2015 工程xlnt 官方提供了 CMake 构建方式但 VS2015 用户如果不想折腾 CMake直接把源码并入工程也是一种非常稳的做法。我建议单独建一个解决方案里面放两个项目xlnt静态库项目和你的xlnt_demo主项目。具体操作步骤如下在 VS2015 里新建一个空的 C 控制台应用命名xlnt_demo。右键解决方案 - 添加 - 新建项目选择 静态库命名xlnt_lib。把 xlnt 源码里的source目录下所有.cpp文件添加到xlnt_lib项目里注意保留目录结构。在xlnt_lib项目属性 - C/C - 常规 - 附加包含目录添加$(SolutionDir)xlnt\include这里的路径以你源码解压的位置为准。在xlnt_lib项目属性 - C/C - 预处理器 - 预处理器定义添加_CRT_SECURE_NO_WARNINGS和XLNT_STATIC避免一大堆安全警告和动态库导出宏的干扰。把xlnt_lib项目的配置类型设为静态库.lib。在xlnt_demo主工程属性里附加包含目录指向include那一层附加库目录指向xlnt_lib的输出目录附加依赖项里加上xlnt_lib.lib。这里有一个特别容易踩的坑项目属性里的 字符集 一定要统一建议全部使用 使用多字节字符集 或者统一用 Unicode混用会导致接口调用时出现奇怪的编译错误。我在一次示例代码里混用了char*字符串和宽字符结果报错信息完全指向了 xlnt 内部排查了半天才发现是自己的字符集设置问题。2.3 运行库和 C 标准版本设置VS2015 默认的 C 标准是 C14 的预览版本xlnt 用到的特性在 VS2015 Update 3 之后基本都能正常编译。如果你还在用老的 Update 1 或 Update 2建议先去装 Update 3否则会碰到一些 lambda 表达式和类型推导的编译错误。运行库的选择上Debug 模式用/MDdRelease 模式用/MD这个要和xlnt_lib项目的设置保持一致。如果不一致链接时会报LNK2038之类的不匹配错误说明_ITERATOR_DEBUG_LEVEL或者运行时库不一致这种报错一般一眼就能看出来但是新手容易懵。2.4 更省事的 vcpkg 方案如果你不介意用 vcpkg命令其实更简单vcpkg install xlnt --triplet x86-windows-static安装完之后用 vcpkg 的 CMake toolchain 文件或者在 VS 里通过 NuGet 包管理器 引用 vcpkg 的输出。不过我用 VS2015 实测下来vcpkg 配合 CMake 比较顺滑纯 VS 工程的话还是手动集成更直接。而且 vcpkg 的静态库版本有时候会和 VS2015 的运行时库打架需要额外花时间调。3. 核心代码解析读写 Excel 的常用操作工程搭好之后就到了最核心的部分——写代码。我把自己在xlnt_demo里沉淀下来的读写模板贴出来每一段都标了注释可以直接拿去做底子。3.1 创建一个新的 xlsx 文件并写入数据#include xlnt/xlnt.hpp #include iostream int write_demo() { // 创建一个工作簿 xlnt::workbook wb; // 获取默认的工作表并重命名 xlnt::worksheet ws wb.active_sheet(); ws.title(年度报表); // 写入表头 ws.cell(A1).value(产品名称); ws.cell(B1).value(销量); ws.cell(C1).value(单价); ws.cell(D1).value(销售额); // 写入两行数据 ws.cell(A2).value(键盘); ws.cell(B2).value(120); ws.cell(C2).value(99.5); ws.cell(D2).value(120 * 99.5); ws.cell(A3).value(鼠标); ws.cell(B3).value(230); ws.cell(C3).value(45.0); ws.cell(D3).value(230 * 45.0); // 设置列宽让它看起来没那么挤 ws.column_properties(A).width 20; ws.column_properties(B).width 12; ws.column_properties(C).width 12; ws.column_properties(D).width 14; // 保存文件 wb.save(report.xlsx); std::cout 写入成功 std::endl; return 0; }这个例子基本涵盖了最常见的写入需求设置单元格值、设置列宽、保存文件。需要特别注意的是wb.save()的路径参数如果传的是相对路径那么是相对于进程当前工作目录的而不是相对于.exe文件所在目录。我自己就因为这个原因在 VS 里调试时发现文件被写到了工程的源码目录下后来又排查了一会儿才反应过来。3.2 读取一个已有的 xlsx 文件#include xlnt/xlnt.hpp #include iostream int read_demo() { xlnt::workbook wb; try { wb.load(report.xlsx); } catch (const xlnt::exception e) { std::cerr 文件加载失败: e.what() std::endl; return -1; } // 遍历所有工作表 for (auto ws : wb.worksheets()) { std::cout 工作表名: ws.title() std::endl; // 遍历所有有内容的行 for (auto row : ws.rows(false)) { for (auto cell : row) { std::string coord cell.reference().column_string() std::to_string(cell.reference().row()); // 根据类型输出不同内容 switch (cell.data_type()) { case xlnt::cell::type::number: std::cout coord : cell.valuedouble() std::endl; break; case xlnt::cell::type::text: std::cout coord : cell.valuestd::string() std::endl; break; case xlnt::cell::type::boolean: std::cout coord : (cell.valuebool() ? true : false) std::endl; break; default: std::cout coord : (空) std::endl; break; } } } } return 0; }读取时最容易犯的错误是不管单元格类型直接cell.valuestd::string()如果单元格里存的是数字这个调用会抛异常。所以务必要先判断data_type()再取值。这段代码在遍历时用rows(false)传了false意思是跳过完全空白的行如果不写这个参数碰到某些带格式但没内容的行时会多出很多无意义的空循环。3.3 修改已有文件并另存int modify_demo() { xlnt::workbook wb; wb.load(report.xlsx); xlnt::worksheet ws wb.active_sheet(); // 把 B2 单元格的值改为 150 ws.cell(B2).value(150); // 重新计算 D2 的公式值 ws.cell(D2).value(150 * 99.5); // 给表头加上加粗样式 xlnt::font bold_font; bold_font.bold(true); ws.cell(A1).font(bold_font); ws.cell(B1).font(bold_font); ws.cell(C1).font(bold_font); ws.cell(D1).font(bold_font); wb.save(report_modified.xlsx); return 0; }修改场景在xlnt里就是读进来、改内存里的对象、再整体保存这样一个流程。它的设计不同于 DOM 或者流式处理所有修改都在内存中完成所以文件不算特别大时性能没什么问题但如果要处理几十 MB 的大型表格内存占用会明显上升到了那个量级就得考虑别的手段了。3.4 遍历单元格区域的常用技巧有时候我们需要读取某一块连续区域比如A1:D10用range接口会更方便auto range ws.range(A1:D10); for (auto row : range.rows()) { for (auto cell : row) { // 每个 cell 都拿到引用坐标和值 } }range.rows()返回的是行视图还可以用range.columns()遍历列。这个接口的效率和直接遍历全表差不多但代码意图会清晰很多。4. 常见问题与排查技巧实录这个 demo 从搭建到写完中间踩了不少坑。我挑几个最具代表性的问题整理成速查表都是实际遇到并解决的问题直接抄作业即可。问题表现可能原因解决方案编译报错C2039: xxx 不是 std 的成员缺少头文件或 C 标准版本过低确认装了 VS2015 Update 3检查包含目录是否完整链接报错LNK2038: 检测到 _ITERATOR_DEBUG_LEVEL 不匹配Debug/Release 或运行时库设置不一致统一所有项目的运行时库/MDd或/MD运行时报xlnt::exception: no such file or directory文件路径不对不是 exe 所在目录用绝对路径或者先打印std::filesystem::current_path()查看工作目录生成的 xlsx 用 Excel 打开提示文件已损坏保存路径有中文字符或特殊字符旧版本 xlnt 对路径编码处理有 bug先保存为纯英文路径测试确认没问题再改路径读取时单元格取值抛异常类型判断错误把一个 number 类型当成 text 读取先判断data_type()再做类型转换性能很慢读取大文件卡顿xlsx 解析需要解压整个 zip内存占用高控制文件大小或者考虑拆分成多个文件处理4.1 中文路径和中文内容导出这是我在 Windows 平台上遇到的最大的坑。xlnt 的内部实现里使用了自己封装的路径处理函数在某些版本下对中文路径的支持并不好保存到带中文的目录时会静默失败或者抛异常。我当时用一个叫测试文件.xlsx的名字保存Excel 打开一直报错但是文件内容在解压看 XML 时又没问题后来才定位到是文件名编码问题。解决思路有两个一是绕开中文路径程序内部统一用英文临时文件等写完再用系统 API 重命名为中文名二是把 xlnt 升级到比较新的版本再测试新版对宽字符路径的支持有所改善但在 VS2015 下我还是建议走英文名方案踩坑率最低。4.2 公式和缓存值xlnt 本身是一个读取写入的库它不会像 Excel 那样实时计算公式。如果你用 xlnt 读取一个包含公式的单元格得到的可能有两种东西公式字符串比如SUM(A1:A3)或者缓存的计算结果取决于文件里是否存储了缓存值。WPS 或 Excel 保存文件时一般会带缓存但有些生成工具生成的文件可能不带。实际应用中我一般不在 xlnt 里依赖公式计算而是自己在代码里算好结果再直接写入数值。如果你确实需要计算公式可以考虑在写完后调用 Excel COM 打开再保存一次但这就又回到了 COM 方案的场景里权衡取舍需要自己把握。4.3 VS2015 的 C 标准兼容性细节VS2015 不是 C11/14 支持最完善的编译器xlnt 在某些模板元编程的实现上比较新老编译器编译时会有些吃力。如果你在编译 xlnt 源码时遇到奇怪报错先找找有没有#include algorithm缺失的问题这类问题在 VS2015 上特别常见因为它的标准库头文件互相包含关系和后来版本不太一样。另外一个实用技巧是在xlnt_lib项目里把警告级别调低到/W2因为 xlnt 自身的源码在 VS2015 下会刷出一堆警告Level 4 级别下几百条警告会影响你发现真正的问题。这是我做这个 demo 时反复被折磨之后的经验。5. 从 demo 到实际项目的扩展方向xlnt_demo只是一个起点实际业务中我们通常还会遇到一些更复杂的需求。我列几个我认为值得继续深挖的方向。5.1 报表模板填充很多内部系统的日报、周报其实都是拿一个固定模板往指定的单元格里填数据。用 xlnt 可以这样玩先把一个 xlsx 文件当作模板用wb.load(template.xlsx)加载然后只修改某些特定的单元格最后另存为一个新文件。这样做的好处是模板里的样式、合并单元格、页眉页脚全部保留代码里不需要去维护这些格式信息。模板里面可以约定好占位符比如{{date}}、{{sales}}打开模板之后再用正则或者字符串匹配替换掉这些占位符。这个方案比逐个单元格设置样式要省太多事了。5.2 数据可视化辅助xlnt 提供了简单的图表生成接口但功能相比 Excel 原生图表还是弱不少。我个人的推荐是图表类需求尽量用 Excel 的模板能力去解决xlnt 只负责准备数据源区域然后模板里的图表引用这个区域刷新后就能出图。这种方式稳定且代码量很少效果还好看。5.3 性能优化策略对于大批量数据写入比如几万行逐单元格写入会相对慢一些。一个常用优化手段是批量提交for (int row 1; row 5000; row) { for (int col 1; col 20; col) { ws.cell(xlnt::cell_reference(col, row)).value(row * col); } }xlnt 在写入时本身是纯内存操作性能瓶颈主要出在保存时对 XML 的序列化上。实测 5 万行、20 列的数据保存时大约在 1-2 秒左右这种量级还是可以接受的。如果你想进一步优化可以考虑关闭 xlnt 的默认压缩等级再保存但那样文件体积会大很多属于拿体积换时间的取舍。5.4 多线程使用注意xlnt 的workbook对象不是线程安全的。如果你的程序里多个线程同时处理不同的 Excel 文件一定要保证每个线程持有一个独立的workbook实例。如果多个线程只能操作同一个 workbook那就需要加锁或者把任务串行化。我见过有人在多线程环境里共享同一个 workbook 实例结果偶尔出现文件损坏排查了很久才发现是并发写入导致的。6. 写在最后的调试心得做这个xlnt_demo项目的时候我最大的一个体会是用开源库处理 Excel 时遇到问题别急着去 GitHub 提 issue先自己动手解压文件看看里面的 XML很多时候问题根源一目了然。比如文件损坏的问题你用压缩工具打开看一下 XML 有没有明显缺漏基本就能判断是数据问题还是库的问题。另一个心得是调试路径问题的小技巧在程序里打印当前工作目录用系统 API 获取 exe 所在路径两者一对比就知道文件被写到哪里去了。这个小习惯帮我节省了大量时间因为 C 里的相对路径概念和很多人的直觉不太一样。还有一点想提的是版本管理。xlnt 的 API 在不同版本之间有过一些调整尤其是枚举类型和部分接口的命名。如果你在网上找到了旧代码直接粘到新版本里不一定编译得过。建议锁定一个版本长期使用不要频繁升级。最后再分享一个小技巧在 VS2015 里调试 xlnt 代码时可以在wb.save()这一行打断点然后单步跟进去看看保存流程对理解它的内部实现非常有帮助。我当时就是顺着这个路径真正搞清楚了 xlsx 的 zip 结构和 xlnt 的序列化关系之后遇到问题心里就有底多了。本文还有配套的精品资源点击获取