ARTICLE DETAIL

资讯详情

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

OpenXLSX:用纯C++读写xlsx,轻松搞定Excel数据报表

OpenXLSX:用纯C++读写xlsx,轻松搞定Excel数据报表 简介OpenXLSX 是一款基于 C17 的开源 C 库用于在原生 C 环境中读取、写入、创建和修改 Microsoft Excel.xlsx文件适合需要在桌面工具、数据分析或业务系统中集成表格处理能力的开发者。它围绕工作簿、工作表、单元格、样式和公式建立了统一抽象支持整数、浮点数、字符串、日期等多种数据类型并可保留常用 Excel 格式。资源共 89 个文件其中 42 个 hpp 头文件和 32 个 cpp 源文件构成完整实现辅以 CMake 构建配置、README 等说明文档和演示代码整个压缩包仅 367KB便于快速下载、编译与移植。目前已有 1006 人学习下载。包内除核心源码外还附带 Test00~Test08 系列测试用例、Demo1~Demo5 示例工程、基准测试程序以及 Doxygen 文档配置内容覆盖从创建文档、读写工作表到设置单元格样式、使用公式与保存文件的完整流程也可作为学习 C17 移动语义、智能指针和模板编程的落地范例。 干活这么多年只要涉及到给业务方交付报表、把程序里的数据导成表格、或者反过来从Excel批量读配置C开发者就难免要跟.xlsx文件打交道。做MFC桌面程序的时候我还试过直接调Excel的COM接口结果客户机器上没装Office程序直接崩了那叫一个酸爽。后来换了思路用纯C去解析xlsx的底层XML自己拼字符串结果拼出来的文件Excel打开要么报错要么乱码维护起来更是噩梦。今天要聊的OpenXLSX就是专门解决这个痛点的。它是GitHub上troldal开源的一个C库核心功能一句话就能说清楚用纯C读写、创建、修改.xlsx格式的Excel文件不需要安装Office不依赖COM组件跨平台编译而且API设计得相当简洁。如果你正在用C做数据报表、批量处理Excel数据、或者写一些自动化工具这篇内容值得花几分钟看完。1. 项目概述OpenXLSX到底解决什么问题1.1 库的基本情况OpenXLSX是一个基于C17标准开发的轻量级库项目地址在GitHub上troldal/OpenXLSX采用MIT协议开源这意味着你可以放心地把它用在商业项目里不需要担心授权费用。这个库支持读取和创建.xlsx格式的Excel文件同时也兼容.xlsm带宏的表格的读取操作。和那些需要完整Excel对象模型的重量级方案不同OpenXLSX走的是文件级解析路线。它直接把xlsx文件当成一个Zip压缩包来处理内部通过解析OOXMLOffice Open XML标准定义的XML文件来还原工作表数据。这个设计思路带来的最直接好处就是运行环境只需要能跑C17不需要安装任何Office组件。我之前在Linux服务器上生成报表就是靠它搞定的接个定时任务就能给业务部门推送Excel格式的数据分析结果。1.2 为什么在C生态里需要它C圈子里处理Excel这事儿一直挺尴尬的。我给你列几个常见方案你就明白痛点在哪了。方案优点缺点调用Excel COM接口功能最完整能操作单元格样式、图表只支持Windows必须装Office崩溃风险高命令行调Python脚本pandas/xlwings代码简单生态成熟部署环境得装Python性能有损耗还得维护两套代码自己解析xlsx底层XML无外部依赖可控性高开发量大遇到合并单元格、样式表、日期格式容易踩坑OpenXLSX纯C实现、无Office依赖、跨平台不支持.xls老格式复杂图表和透视表支持有限我之前用COM接口写过一个报表程序测试环境一切正常结果客户那边有个精简版Office整天出幺蛾子。后来把核心功能全部迁到OpenXLSX世界瞬间清净了。它的使用场景非常明确自动化报表生成、批量读取Excel配置数据进行处理、把程序计算结果写入模板文件。这些场景下我们需要的不是把Excel的所有功能都搬过来而是稳定可靠地读写数据。2. 核心细节解析xlsx格式原理与OpenXLSX的设计巧妙之处2.1 xlsx文件本质上是一个Zip包很多人以为.xlsx是某种复杂的二进制格式其实不是。你可以自己验证一下把一个xlsx文件复制一份把后缀改成.zip然后解压会看到里面是一堆XML文件。这就是OOXML格式的核心思想——用XML描述电子表格的全部内容。打开解压后的目录你会看到这些结构[Content_Types].xml声明包内各个部件的内容类型_rels/.rels包级别的关联关系xl/workbook.xml工作簿的全局信息比如包含哪些工作表xl/worksheets/sheet1.xml第1个工作表的单元格数据xl/sharedStrings.xml共享字符串表所有单元格里的文本都汇总在这为什么要搞共享字符串表因为同一个文本可能在几百个单元格中出现把它单独存一遍所有单元格用索引引用文件体积就能大幅缩小。Excel工程团队在设计这个格式的时候确实为大数据量场景做了不少优化。2.2 OpenXLSX的解析思路OpenXLSX的底层是把Zip包里的XML文件读出来再用DOM文档对象模型方式解析。它内部封装了这些结构XlWorkbook对应整个工作簿是你操作的入口XlWorksheet对应单个工作表负责单元格的读写XlCell对应一个单元格封装了值、类型、样式等信息XlCellRange单元格区域可以批量操作行列这个设计的精妙之处在于它把复杂的XML解析过程全部隐藏在底层对外暴露的是符合直觉的面向对象接口。比如你想往工作表里写数据直接创建一个cell然后赋值就行完全不用关心这个值应该写到XML的哪个节点、是不是需要进sharedStrings表。库内部会自动处理类型推断、字符串表维护、行列索引转换这些琐碎的细节。2.3 为什么说文件级操作是务实的选择有人可能会问既然Excel自带的COM接口功能那么全为什么还要用OpenXLSX这种文件级方案关键差别在于COM接口操作的是Excel进程的内存对象每次调用都要跨越进程边界性能开销大而且只要Excel进程崩溃整个调用链就断了。OpenXLSX直接操作磁盘上的文件内容不做进程间通信所以执行效率极其稳定。举个例子我之前一个需求是给一批数据文件批量生成汇总表每个文件大概有1万行数据用COM接口写一次要十几秒用OpenXLSX基本上是毫秒级。这种性能差异在实际生产环境中非常明显。当然如果你想在程序里嵌入一个完整的Excel界面那还是得走COM或者用Qt的表格组件那是另一个话题了。3. 实操指南从环境配置到写出第一个xlsx文件3.1 获取和集成OpenXLSXOpenXLSX本身是源码分发的推荐用CMake的FetchContent方式集成到项目里非常省事。在项目的CMakeLists.txt里加上这么一段include(FetchContent) FetchContent_Declare( OpenXLSX GIT_REPOSITORY https://github.com/troldal/OpenXLSX.git GIT_TAG v0.4.2 ) FetchContent_MakeAvailable(OpenXLSX) target_link_libraries(your_target PRIVATE OpenXLSX::OpenXLSX)这里有个小提醒如果你需要指定某个稳定版本就把GIT_TAG换成对应的release tag。直接拉最新main分支有时候会踩到主分支上尚未发布的新特性带来的编译问题这点我用过好几次踩过坑。如果你不喜欢FetchContent也可以把源码clone下来用CMake单独构建成静态库再链接到自己的工程里。无论哪种方式都要求你的编译器支持C17。我在GCC 9、MSVC 2019、Clang 12上都编译通过过兼容性没什么大问题。3.2 核心操作创建、写入、读取先看一段最常用的代码——创建一个新工作簿写入数据然后保存。这种场景常用于程序自动生成报表#include OpenXLSX.hpp #include iostream using namespace OpenXLSX; int main() { // 1. 创建一个新的工作簿 XlWorkbook workbook; auto worksheet workbook.workbook(). worksheet(LSheet1); // 2. 写入数据A1单元格放标题A2放数字B2放文本 worksheet.cell(LA1).value() 项目报表; worksheet.cell(LA2).value() 42; worksheet.cell(LB2).value() 完成; // 3. 保存到本地文件 workbook.saveAs(Lreport.xlsx); std::cout 成功创建 report.xlsx std::endl; return 0; }需要注意的一个细节是上面的代码用到了.cell()这个API它返回一个XlCell对象的引用通过.value()赋值。如果你定义一个auto变量来接收cell对象一定要用引用类型比如auto cell worksheet.cell(LA1);否则会复制一份临时对象赋值操作不会生效。这是我的亲身教训当时排查了半天。再来看读取操作。这个在批量配置文件导入、读取其他系统导出的数据时常用#include OpenXLSX.hpp #include iostream using namespace OpenXLSX; int main() { XlWorkbook workbook; workbook.open(Linput.xlsx); auto worksheet workbook.workbook(). worksheet(LSheet1); // 读取单个单元格 auto cell worksheet.cell(LC5); if (cell.value().type() XLValueType::Integer) { int val cell.value().asint(); std::cout C5: val std::endl; } // 遍历一整列数据 for (uint64_t row 1; row worksheet.rowCount(); row) { auto value worksheet.cell(row, 2).value(); // 第B列 std::cout Row row : value.asstd::string() std::endl; } return 0; }这里有个特别值得讲的知识点cell.value()返回的是XlValue对象它是一个变体类型支持整数、浮点数、字符串、布尔值、空值等多种类型。读取时最好先用.type()检查数据类型再做转换避免类型不匹配时抛出异常。我在实际开发中见过很多人直接asstd::string()然后程序崩了其实就是数据本身是数字类型强转失败。3.3 进阶玩法工作表管理、公式、合并单元格日常工作中光读写单元格是远远不够的。OpenXLSX支持的工作表操作包括添加、删除、重命名工作表格式化列宽行高合并单元格写入公式等。我挑几个高频功能演示一下。添加工作表并写入数据auto newSheet workbook.workbook(). addWorksheet(L汇总数据); newSheet.cell(LA1).value() 季度; newSheet.cell(LB1).value() 销售额; newSheet.cell(LA2).value() Q1; newSheet.cell(LB2).value() 128000;合并单元格通常用来做表头// 把A1到D1合并作为整张表的标题 worksheet.mergeCells(XLCellRange(LA1:D1)); worksheet.cell(LA1).value() 2024年度销售汇总;写入公式要注意OpenXLSX会把以开头的内容识别为公式但它本身不负责计算公式的结果。Excel打开文件时会自动重新计算。这是符合标准的。worksheet.cell(LB3).value() XlFormula(LSUM(B1:B2));如果你需要在程序里直接拿到计算结果就得上别的重型方案了OpenXLSX定位是数据读写层公式计算引擎不在它的范畴内。3.4 大文件读写的性能调优心得连续写几万行数据的时候如果逐单元格写入性能其实还可以接受但有一个点值得优化减少不必要的查询操作。比如获取cell对象时如果用字符串形式的地址LA1每次都要解析单元格地址转换成行列索引开销比较大。正确的做法是如果能用行列索引就不要用单元格地址字符串。一个几十万行的大文件这个优化能省下不少时间。另一个性能相关的细节是当你只需要读取某个区域的数据时可以先用XlCellRange来限定读取范围避免遍历整张工作表。如果文件里有大量空白行列直接遍历rowCount()会做很多无意义的查询。我实际测试过用OpenXLSX写一个10万行、10列的表格耗时大概在2到3秒之间读相同规模的文件也差不多。这个性能对大多数业务场景都够用了除非是处理百万行级别的超大数据集那种情况建议考虑更底层的流式解析方案。4. 常见坑与排查技巧实录4.1 编译链接阶段的问题用CMake集成OpenXLSX时如果编译报错说找不到OpenXLSX.hpp头文件先检查target_link_libraries里有没有加上OpenXLSX::OpenXLSX。这是最基础的。另一个高频坑是C标准版本不对。OpenXLSX使用了C17的std::filesystem、std::optional等特性如果你在CMakeLists里忘了设置set(CMAKE_CXX_STANDARD 17)编译器会抛一堆“找不到std::variant”之类的错误。这里我建议直接在项目里写死set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON)还有MSVC的运行时库问题。如果你的主程序是用/MD动态运行时编译的而OpenXLSX是用/MT静态运行时构建的链接阶段会出现各种_ITERATOR_DEBUG_LEVEL不匹配的报错解法是保持两边的运行时库一致。4.2 中文乱码与编码问题xlsx的编码格式是UTF-8库内部处理字符串时用的是std::string但字符串字面量要小心。在Windows上如果你写worksheet.cell(LA1).value() 中文内容;MSVC的窄字符串字面量是本地代码页编码通常是GBK不是UTF-8写出来的文件在Excel里打开就会乱码。解决方案有两种一是把源文件保存为UTF-8 with BOM这样MSVC会把窄字符串字面量识别为UTF-8二是用u8中文内容来构造UTF-8字符串。我在Windows上做开发时常用第二种比较省心worksheet.cell(LA1).value() std::string(u8产品名称);注意OpenXLSX的value()方法接收的是std::string所以就算你用了宽字符的L...最终写入数据的还是一个字节流编码格式一定要保证是UTF-8。4.3 单元格类型的坑用cell.value().asT()做类型转换时偶尔会遇到XLValueType不匹配导致的异常。比如Excel里某列数字本来就是文本格式里面存了“12345”读出来类型是String而非Integer。如果直接asint()就会异常。稳妥的做法是先把XlValue转成字符串再做类型转换std::string str cell.value().asstd::string(); int num std::stoi(str);这样做兼容了数字和文本两种场景代价是增加了字符串转换的开销但对一般规模的数据几乎无感。还有一个高频问题读取空单元格。如果某一行有一个空格或者直接是空值OpenXLSX读取时会返回一个XlValue对象其类型是XLValueType::Empty。此时调用asstd::string()会返回空字符串调用asint()会抛异常最好先做类型判断。4.4 格式兼容性与模板文件如果你的程序是往一个已有格式的Excel模板里填数据要注意OpenXLSX对样式字体、颜色、边框的保留能力有限。它能保留模板中大部分内置格式但如果你在代码里修改单元格样式API支持的样式控制还不够细。推荐的做法是把模板的格式信息在Excel里预先设计好程序只负责填数据和改值不要试图在代码里大改样式。我在做对接业务部门的报表程序时就是先让运营同事用Excel做好模板带好格式、公式、条件样式然后我用OpenXLSX打开这个模板把数据填进去另存为一份新报告。这样既保留了Excel原生的美化效果又不需要去跟复杂的样式XML打架两边都爽。5. 工具选型参考OpenXLSX和其他方案怎么选5.1 OpenXLSX适合什么场景根据我的使用经验下面这些场景优先考虑OpenXLSX跨平台桌面程序Windows、Linux、macOS都要跑又不愿意装Office服务端批量生成ExcelWeb后端用C写服务定时跑数据报表推送数据导入导出工具配置表读取、数据迁移、脚本化Excel处理对性能有要求处理几万到几十万行数据的表格不希望在跨进程调用上耗太多时间5.2 OpenXLSX不适合什么场景也得实话实说有些场景OpenXLSX并不合适需要操作宏VBA虽然能读.xlsm文件但执行宏是做不到的那是Excel应用层的功能复杂图表处理OpenXLSX对图表的支持很有限插入简单的图片可以复杂交互图表别指望需要精确控制打印版面、页眉页脚这类格式化细节目前API覆盖不完整容易踩边角问题.xls老格式文件这个库明确不支持老式的二进制Excel文件遇到.xls得先转换格式我在一次需求里就遇到了.xls旧文件处理那时候我把方案改成了先让用户在系统里转换或让脚本用LibreOffice批量转换一下再进来处理。5.3 和其他C库的同台对比特性OpenXLSXSimpleXlsxWriterlibxlsxwriter读取xlsx支持不支持不支持修改已有文件支持不支持不支持跨平台是是是公式支持写入可用写入可用写入可用样式控制基础基础较丰富内存占用较低低低OpenXLSX最独特的优势在于它同时支持读取和修改已有文件如果只是单纯地生成新的xlsx文件libxlsxwriter也是一个不错的选择。但凡是涉及打开模板填数据、动态增删行这类需求OpenXLSX几乎是唯一能满足的开源C库。6. 从项目实践看OpenXLSX的完整流程6.1 一个完整的报表生成案例我在之前的项目里负责过一个燃气生产调度数据汇总功能需求是每天凌晨从数据库读取前一天的生产数据生成一份Excel日报表发给相关科室。这个业务让我把OpenXLSX的完整流程走了一遍。核心的处理流程是这样的从数据库按班组、站点、时间维度查询生产数据得到原始记录用OpenXLSX打开一个设计好的日报模板模板里有标题、表头、公式字段把查询到的数据逐行写入对应区域删除无用行调整部分列宽另存为日报_日期.xlsx上传到内部系统供下载过程中我体会最深的是模板化的必要性。把涉及格式的部分尽量交给Excel设计器去做不要用代码去构造复杂的格式。我在代码里只做了很基本的单元格赋值操作写完就保存业务方对报表格式非常满意。因为模板本身的样式是他们在Excel里精心设计的要什么颜色、边框、字体都有。6.2 业务侧对OpenXLSX的使用要点总结如果你要把这个库引入到团队项目里有几个工程化建议封装一层业务接口不要让业务代码直接和OpenXLSX的API耦合方便以后替换底层库或者加缓存、日志写入大批量数据时考虑分批和避免频繁的单元格地址解析在设计模板时提前确认好哪些单元格是可填区域程序侧写死这些区域避免模板被改动导致程序出错线程安全方面OpenXLSX本身不是线程安全的多个线程同时操作同一个工作簿需要你自行加锁不同线程操作不同工作簿则没有限制写在最后的个人体会用过一段时间OpenXLSX后我最大的感受是这个库把“操作Excel”这件事的复杂度隔离得很好。你不需要去理解OOXML那一堆规范也不需要考虑Zip包的内部结构更不需要关心Excel程序是否安装只需要按直觉写几行代码工作就完成了。根据我个人的选型经验如果你正在写一个C程序需要跟xlsx文件打交道先别急着去调COM接口或者外包给Python脚本试试OpenXLSX大概率能满足你的需求。在稳定性和部署便利性上它会给你带来不少惊喜。最后再说一个小技巧如果你要在Linux服务器上做测试记得用LibreOffice转出一个xlsx样例文件OpenXLSX存文件后也可以用LibreOffice转换成PDF用来看渲染效果调试效率高很多。本文还有配套的精品资源点击获取
返回列表