ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

VS2017集成libxlsxwriter:从源码编译到Excel导出实战

VS2017集成libxlsxwriter:从源码编译到Excel导出实战 简介面向需要在Windows平台使用libxlsxwriter库的C/C开发者尤其适合VS2017编译环境下的项目集成场景。资源包为rar压缩格式整体大小29.04MB内含已编译好的zlib库、libxlsxwriter.lib库文件并附带配置完成的VS2017工程开发者可直接将库链接到现有项目或打开工程对照使用省去从源码编译zlib和libxlsxwriter的繁琐步骤也能避免因版本或运行环境不一致导致的链接错误。目前已有526人学习下载适用于需要快速生成Excel .xlsx报表、处理单元格格式、分批写入数据的场景。它既保留了libxlsxwriter完整的API能力又通过预编译库和现成工程降低了上手门槛压缩包内库目录与工程文件分离引用路径清晰可作为VS2017中集成xlsx写入功能的样板。1. 为什么我在VS2017里折腾libxlsxwriter一个绕不开的现实问题先交代下背景。最近在维护一个老项目整套工具链还停在VS2017上需要把程序里的报表导出功能从CSV升级成真正的xlsx格式。CSV这玩意儿在财务同事那儿已经引发过好几次“打开乱码”“格式全丢”的投诉了所以这次必须一步到位直接生成能双击打开、带格式、带公式的Excel文件。选型的时候其实没什么悬念。网上搜了一圈能用的C/C方案无非那么几个直接操作XML写xlsx、用微软自家的Office COM接口、或者接第三方库。COM接口在老项目里接入成本太高还得装Office环境手撕XML太容易踩坑一个标签写错整个文件就废了。这时候libxlsxwriter进入视野——它的定位就是“用纯C代码生成xlsx文件”不依赖Office不依赖平台编译出来一个静态库就能用。这里先给第一次听说libxlsxwriter的朋友说清楚它是什么这是一个用C语言编写的开源库用来生成Excel 2007及以上版本的xlsx文件。注意它只负责“写”不负责“读”也不负责在界面上展示表格。它的核心价值在于你可以用代码描述“第几行第几列写什么内容、什么字体、什么颜色、要不要合并单元格、要不要加公式”然后它帮你把背后的XML、压缩包这些脏活累活全干完。我需要的是在VS2017里把这份能力接进来。乍一听很简单但实际动手才发现这个库的构建方式对Windows用户相当不友好。尤其是VS2017这个版本既不像VS2022那样有现成的社区支持又不像更老的VC6那样有远古教程可抄。这篇文章就把我从下载源码到最终跑通发布版本的完整过程记录下来包括中间踩过的坑和排查链路希望能让你少走几步弯路。2. 编译前的准备拿源码、凑工具、确认VS2017环境2.1 源码获取与目录结构速览libxlsxwriter的源码托管在GitHub上直接克隆或者下载zip包都行。值得注意的是这个库很贴心地提供了三个层次的接口纯C API官方主推所有语言绑定的底层都是它C封装是别人贡献的不是官方主推渠道Python、Ruby、Lua等语言的扩展模块咱们在C/C项目里用直接走官方C API就够了不需要额外的语言绑定层。下载下来的源码目录里真正需要关注的只有几个地方src/全部C源码包括核心的worksheet、workbook、format这些模块include/对外暴露的头文件就一个xlsxwriter.h包含所有API声明third_party/内置的依赖库包括minizip和zlib的精简版本Makefile和makefile基于GNU Make的构建脚本这里要多说一句这个库在Windows上默认推荐的构建方式是使用GNU Make而不是直接给你一个.sln文件。这就意味着如果你用的是VS2017要么想办法让VS2017的构建流程去调用GNU Make要么自己动手把源码加到VS工程里编译。前者的坑更多后者反而更可控。我的建议是直接用“把源码加进工程”这条路。2.2 确认VS2017版本和工具集VS2017这个版本稍微有点特殊它既不是老掉牙的VC6/VC2008时代也不是最新的VS2022很多第三方库的预编译包根本不提供对应版本。打开VS2017的“关于”对话框看到的版本号一般是15.x系列对应的原生C工具集版本是v141。在正式动手前先检查一下你是否装了C桌面开发组件。打开Visual Studio Installer找到“使用C的桌面开发”这个工作负载必须已经勾选。如果没有先补装否则后面编译C源码的时候会提示找不到cl.exe或者rc.exe。还有一个容易被忽略的点Windows SDK版本。建议装10.0.17763以上版本太老的SDK在某些标准库头文件的兼容性上会有问题。2.3 准备编译辅助工具虽然我们打算手动把源码拉进VS工程但肯定不可能把所有.c文件一个个拖进来那太蠢了。这里推荐两个辅助工具Visual Studio的“现有项”批量添加功能在解决方案资源管理器里选中项目右键“添加”-“现有项”可以一次选中src目录下所有.c文件Python脚本或者批处理如果你以后需要经常重建这个依赖建议写个小脚本自动从源码目录同步文件列表我当时是直接在VS2017里手动把src目录下所有.c文件全部选中加入工程大概二十多个文件一次搞定并不需要自动化工具因为这种依赖集成是一次性的工作。3. 把libxlsxwriter源码塞进VS2017工程的完整操作3.1 新建工程还是改造老工程取决于你的实际需求两个选择如果你只是想快速验证一下这个库能不能用新建一个空的控制台工程把源码全部加进去写个demo跑通如果你是像我一样要集成进老项目那就直接把源码加进现有工程同时配置好头文件路径和宏定义无论哪种情况后续的配置步骤都一样。下面以“新建一个测试工程并集成成功后再搬到老项目”为例这是最稳妥的做法。3.2 添加源文件和头文件路径新建一个控制台应用工程Win32 Console Application在解决方案资源管理器里找到“源文件”文件夹右键 - 添加 - 现有项定位到libxlsxwriter源码的src目录全选所有.c文件确认添加。接下来处理头文件。在工程属性里找到“C/C” - “常规” - “附加包含目录”把下面几个路径加进去注意填成你本地实际的路径D:\libs\libxlsxwriter\include D:\libs\libxlsxwriter\srcinclude目录下才是xlsxwriter.h的所在位置src目录下也有一些内部头文件被其他源文件包含所以两个路径都要加。漏掉任何一个编译时就会出现找不到头文件的错误。3.3 宏定义与运行时库的一致性这是最容易踩坑的地方展开细说。libxlsxwriter源码里用了一些条件编译宏集中在头文件和少数几个源文件中。其中有一个宏_CRT_SECURE_NO_WARNINGS必须定义否则在VS2017的默认警告级别下fopen、strcpy这类函数会疯狂报C4996警告。虽然只是警告不是错误但满屏的警告会让你很难发现真正的问题。打开工程属性 - “C/C” - “预处理器” - “预处理器定义”把下面两个加进去_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE还有一个非常重要的点运行时库的选择必须统一。libxlsxwriter是个静态库风格的源码集成方式它编译出来的目标代码会直接链进你的可执行文件。所以你的工程里“C/C” - “代码生成” - “运行时库”这个选项设置为/MTdDebug版release源码时如果有调试信息需求或者/MTRelease版。不要再选/MD或/MDd否则链接的时候会报一堆LNK2038运行时库不匹配的错误。这里我要解释一下为什么会有这个限制。VS的C运行时库有动态版/MD和静态版/MT之分动态版依赖msvcp140.dll之类的运行库文件静态版则把所有运行时函数直接编进目标文件。如果你一部分代码用/MT编译另一部分用/MD编译链接器会检测到_ITERATOR_DEBUG_LEVEL或者_STATIC_CPPLIB这些宏的不一致直接拒绝链接。libxlsxwriter源码本身对这两种方式都能编译但你的整个工程必须统一。老项目如果一直用/MD那就在这里统一成/MD即可。不过我的建议是静态链接/MT更省心省得换机器部署时还要带一堆DLL。3.4 编译并处理首次报错配置完成后直接按F7编译。这个库的代码质量相当高理论上在VS2017下不会有太多编译错误。不过我第一次编译时还是踩了个坑报错信息大致是error C4996: localtime: This function or variable may be unsafe.这个是因为Windows SDK里默认推荐用localtime_s。解决方法是前面加的_CRT_SECURE_NO_WARNINGS宏如果你已经加了应该不会遇到。如果还是报错检查一下宏定义有没有生效或者有没有被其他头文件里的#undef干掉。编译成功后会生成一个体积不小的.obj列表最终链接出exe。到这里库的编译集成就已经完成了下面开始写代码验证。4. 第一个能跑的Demo从写入单元格到生成完整表格4.1 最简代码生成带格式的xlsx下面这段代码是我测试时用的功能很朴素创建一个工作簿写两行数据设置一下列宽再保存。但它能验证的核心链路已经涵盖了workbook创建、worksheet添加、格式创建、单元格写入、文件保存。#include xlsxwriter.h int main(void) { lxw_workbook *workbook workbook_new(demo.xlsx); lxw_worksheet *worksheet workbook_add_worksheet(workbook, Sheet1); lxw_format *title_format workbook_add_format(workbook); format_set_bold(title_format); format_set_font_color(title_format, LXW_COLOR_WHITE); format_set_bg_color(title_format, LXW_COLOR_BLUE); format_set_align(title_format, LXW_ALIGN_CENTER); worksheet_write_string(worksheet, 0, 0, 项目, title_format); worksheet_write_string(worksheet, 0, 1, 数量, title_format); worksheet_write_number(worksheet, 1, 0, 100.5, NULL); worksheet_write_number(worksheet, 1, 1, 25, NULL); worksheet_set_column(worksheet, 0, 0, 20, NULL); worksheet_set_column(worksheet, 1, 1, 12, NULL); workbook_close(workbook); return 0; }这段代码背后的接口调用逻辑是这样的workbook_new负责创建xlsx文件的“工程文件”workbook_add_worksheet添加一个工作表workbook_add_format创建一个格式对象。格式对象在xlsxwriter里的设计很有意思它是一个可复用的配置实体同一个格式对象可以套用到任意多个单元格上这比Excel里“每个单元格都有独立格式”的概念要轻量得多。4.2 编译链接可能遇到的问题如果你按照上面的配置把源码全部加进来了链接时应该不会缺符号。但有一种情况你只添加了部分.c文件或者不小心漏掉了某个模块链接时就会报类似这样的错误unresolved external symbol workbook_new referenced in function mainworkbook_new的实现在workbook.c里这类错误说明你没有把对应的.c文件加进工程。解决方法很简单检查一下src目录下是不是所有.c文件都加入了工程一个都别漏。4.3 确认生成文件的正确性运行程序后会在工程目录下生成demo.xlsx。用Excel打开看看如果一切正常说明libxlsxwriter和VS2017的集成已经打通了。这里有一个容易忽略的细节如果你的Excel是WPS也能打开但个别复杂格式比如某些图表或者条件格式在WPS和Excel之间的渲染效果会有差异这属于正常现象不是说文件坏了。还有另一种验证方式把生成的xlsx文件后缀改成.zip用压缩软件打开可以看到里面有一套规范的XML文件结构[Content_Types].xml _rels/.rels xl/workbook.xml xl/worksheets/sheet1.xml xl/styles.xml这套结构其实就是xlsx文件的本质——一个zip压缩包里面装着一堆XML描述文件。libxlsxwriter的职责就是帮你生成并打包这套XML。理解这一点后你会更容易明白为什么它能做到跨平台、不依赖Office。5. 我踩过的坑libxlsxwriter在VS2017下最常见的四个问题5.1 找不到头文件的假象这个问题出现得最多但根本原因多种多样。常见场景是#include xlsxwriter.h编译时提示找不到但你明明已经把include目录加到了附加包含目录里。排查思路是这样的先确认你加的是“附加包含目录”Additional Include Directories而不是“附加库目录”Additional Library Directories这两个位置经常被搞混确认添加的是include目录不是src目录也不是源码根目录。虽然第二级目录可能也能找到头文件但不符合规范如果你用的是相对路径确认当前工作目录是哪个。VS2017的默认工作目录是$(ProjectDir)如果你把库放在工程目录外面相对路径很容易搞错建议直接用绝对路径5.2 编译报错Cannot open include file: unistd.h这个错误是Windows平台上特有的。libxlsxwriter源码中某些文件引用了unistd.h这是Unix/Linux下的头文件Windows上根本没有。正常情况下libxlsxwriter的源码里会通过条件编译来规避这个问题但如果你下载的是比较老的版本或者某些平台的适配代码没有覆盖到就会触发这个错误。解决方案有两个在工程里加一个空的unistd.h头文件放到附加包含目录里让预处理器能找到它。这个文件里只需要写上#pragma once即可因为libxlsxwriter引用它只是需要几个函数声明而这些声明在其他Windows头文件里已经有了更推荐的做法升级到最新版libxlsxwriter。官方在新版本里已经完善了Windows下的头文件适配逻辑不需要手动加空文件我之所以说更推荐升级而不是打补丁是因为这种“加空头文件”的做法虽然能骗过编译器但如果你后续用到某些依赖该头文件的函数可能会在链接期才暴露问题。既然官方已经修了没必要自己在旧版上做土法补丁。5.3 链接阶段出现大量LNK2038或LNK2005错误这类错误出现的场景很典型你把libxlsxwriter源码加进了一个老项目而老项目用的是动态CRT不同编译单元之间宏定义不一致导致符号冲突。排查顺序应该是打开工程属性搜索“运行时库”确认所有配置Debug/ReleaseWin32/x64都统一了检查有没有哪个.c文件被重复添加进工程。重复添加时编译器会先报LNK2005紧接着是一堆重定义错误检查预处理定义里是否同时存在_DEBUG和NDEBUG这也会引发异常我还见过一种情况工程里同时引入了另一个第三方库的源码那个库自己实现了和libxlsxwriter同名的某些基础函数。这种冲突就比较难查了建议先用vs2017的“生成-重新生成解决方案”清理一次然后把所有非标准库的.c源文件逐个排查看看有没有重名符号。5.4 生成的文件在Excel里打不开提示文件损坏这个问题的根源往往不在代码逻辑而在文件被占用或路径问题。典型场景是程序调用workbook_close时返回了错误码但你忽略了它或者输出文件被Excel/其他进程占用无法写入。另外要注意当前工作目录。VS2017调试模式下默认工作目录是$(ProjectDir)也就是工程文件所在的目录。如果你在代码里用相对路径创建文件它会生成在工程目录下而不是生成在Debug或Release输出目录里。很多人在文件夹里找不到生成的xlsx文件以为程序没跑成功其实文件生成到了另一个目录。建议在代码里显式指定完整输出路径比如lxw_workbook *workbook workbook_new(D:\\workspace\\demo\\output\\demo.xlsx);这样就不会因为工作目录问题找不到文件了。6. 进阶用法公式、格式、单元格合并这些API怎么组合6.1 公式的写入方式如果你只是把静态数据写进Excel那和原来的CSV方案没本质区别。真正体现xlsx价值的是能写公式。比如你要在C1单元格里写A1和B1的求和公式worksheet_write_formula(worksheet, 2, 2, A1B1, NULL);这里有个小坑需要注意libxlsxwriter写入的公式在文件里是原始公式字符串Excel打开后会重新计算求值。但如果你用某些第三方库比如Apache POI打开同一份文件可能拿不到缓存的计算结果因为libxlsxwriter默认不缓存值。对于大多数场景这不是问题毕竟Excel打开时一定会计算。但如果你的下游系统依赖“读取xlsx时直接获取缓存值”就要在写入公式时额外指定结果缓存用worksheet_write_formula_num这个接口。6.2 格式的精髓先创建后复用不要为每个单元格新建格式libxlsxwriter里的lxw_format对象数量和生成文件的体积直接相关。Excel的xlsx格式对样式做了去重压缩它能记住哪些单元格共用同一套样式。但如果你为每一行数据都workbook_add_format一次哪怕格式设置完全一样生成的文件里也会有大量重复的样式定义导致文件膨胀、打开变慢。正确的做法是把用到的格式集中定义好作为“样式池”所有单元格引用同一个lxw_format *lxw_format *header workbook_add_format(workbook); format_set_bold(header); format_set_border(header, LXW_BORDER_THIN); lxw_format *money workbook_add_format(workbook); format_set_num_format(money, $#,##0.00);然后写入时直接传header或money。这在数据量大的报表里能明显减少文件体积。6.3 合并单元格和列宽控制的组合报表场景里“合并单元格”几乎必然遇到。比如标题行跨多列居中worksheet_merge_range(worksheet, 0, 0, 0, 4, 季度销售汇总, title_format);merge_range的参数是起始行、起始列、结束行、结束列、内容、格式。注意合并后该区域的左上角单元格承载内容和格式其他单元格会被清空。如果你希望合并区域的每一列宽度都合适还需要配合worksheet_set_column设置列宽否则文字会被截断。6.4 日期时间的写入这是另一个高频需求。Excel里的日期本质上是数字需要一个数字格式来让它显示成日期。libxlsxwriter提供了worksheet_write_datetime接口需要传入一个lxw_datetime结构体lxw_datetime datetime {2024, 5, 20, 14, 30, 0.0}; lxw_format *date_format workbook_add_format(workbook); format_set_num_format(date_format, yyyy-mm-dd hh:mm:ss); worksheet_write_datetime(worksheet, 1, 0, datetime, date_format);这里有个经典坑结构体里年份字段的类型是int16_t月份和日期是int8_t但如果你赋值时直接写2024, 5, 20编译器会做隐式转换一般不报错。但如果你用了变量且类型不匹配会有截断风险建议赋值时注意类型转换。7. 性能与体积大数据量写入时的实测体会7.1 数据量到达多少时需要考虑性能libxlsxwriter底层的写入机制是先把数据缓存在内存里直到workbook_close时才真正写入并压缩。所以如果你一次性写入几十万行数据内存占用会相当可观。我在一个导出场景里实际压测过写入10万行、20列的数据每列都是数字和短字符串混合。编译成Release版本内存峰值大约在300-400MB耗时在3-5秒之间。这个性能对绝大多数业务报表场景已经足够。但如果你要做的是几百万行的超大数据量导出libxlsxwriter就有些吃力了你可能会需要改用专业的ETL工具或者流式写入的库。7.2 如何降低内存占用和提升速度减少格式对象的数量。上面讲过格式复用能减少内存消耗这个影响会随着数据量升高而放大用worksheet_write_string而不是worksheet_write_rich_string处理普通文本。富文本格式需要额外的内存分配非必要不启用开启压缩级别调整。libxlsxwriter内部使用zlib压缩XML默认压缩级别已经比较合理。你可以在workbook_new后调用workbook_set_options或直接改源码里的压缩参数但不建议降到0否则文件体积会大幅上升如果数据是纯数值型的优先用worksheet_write_number它的处理开销比字符串小得多7.3 Release vs Debug性能差异我实测过同一个导出逻辑在Debug和Release下的耗时差异Debug下大约需要25秒Release下只需要4秒。差距有6倍以上。如果你在做性能验证务必用Release版本测Debug的耗时没有参考价值还会误导你得出“这个库太慢”的错误结论。8. 集成到真实项目的最后一步动态库还是静态库以及部署注意事项8.1 为什么我最终选择静态集成libxlsxwriter官方文档推荐两种集成方式静态库把编译好的.lib文件链接进你的程序动态库运行时加载.dll文件在VS2017的老项目里我强烈建议用静态链接。原因有三xlsx文件生成逻辑属于业务核心你肯定希望它跟着主程序走不想额外分发DLL文件静态链接可以避免DLL地狱——不同版本的libxlsxwriter对VS运行时的依赖不同如果你机器上还装了其他软件携带了旧版DLL很容易出现版本冲突这个库本身不大静态链接后增加的体积一般在几百KB到1MB左右完全可接受8.2 生成静态库的正确姿势如果你不想每次都在工程里塞二十几个.c文件可以先把libxlsxwriter编译成一个独立的静态库工程。操作步骤大致是新建一个“静态库工程”Static Library把所有.c文件添加进去配置好头文件目录和预处理宏编译生成xlsxwriter.lib在真正的业务工程里附加库目录指向这个.lib文件所在目录附加依赖项里填上xlsxwriter.lib记得把头文件目录也加到业务工程的附加包含目录里这样业务工程的源码会清爽很多编译也更快。唯一的缺点是如果libxlsxwriter升级了新版本你需要重新编译静态库再更新两个文件。8.3 部署时注意的点静态链接模式下部署时只需要带上你的exe文件即可。如果你在代码里用了workbook_new并在程序结束后调用workbook_close记得检查返回值。workbook_close返回LXW_NO_ERROR才是正常关闭如果返回LXW_ERROR_ZIP_FILE_OPERATION说明文件写入过程中有问题通常是磁盘空间不足或者路径不可写。另外强烈建议在调用workbook_new时使用UTF-8编码的路径字符串。libxlsxwriter内部对路径的处理和VS2017的宽字符集有差异如果你的路径包含中文字符可能出现创建文件失败或乱码文件名。稳妥的做法是把目标目录固定为纯英文路径或者自己先用宽字符API创建目录再传给workbook_new。9. 基于个人经验的额外建议哪些情况下别用libxlsxwriter写这篇文章的过程中我一直在想一个问题为什么有人会选择libxlsxwriter又为什么有人会在用了一段时间后弃用它这个问题没有标准答案但以我带过几个项目的经验来看下面几种情况需要慎重如果你需要读取xlsx文件不要用libxlsxwriter它是纯写库读取功能是零。你需要配合libxls、xlsxio或者直接转为解析XML。别指望它能“顺便读一下”。如果你需要图表libxlsxwriter支持一部分图表功能但支持的图表类型有限而且样式控制不如Excel本身灵活。如果报表对图表要求很高建议改用前端方案比如用ECharts生成图片后再嵌入Excel。如果你的软件需要运行在极其老旧的Windows XP/Server 2003上VS2017编译出来的程序本身就可能不支持这些系统你需要降级到VS2013或者使用更老的工具集。这种情况会导致libxlsxwriter的集成方式也需要重做。反过来说如果你的需求就是“在C/C程序里快速生成一个格式规范、体积可控、能在任意机器上打开的xlsx文件”那libxlsxwriter就是当前最好的选择之一。它的API设计对C语言用户非常友好几乎每个函数名都能望文生义文档也写得很清楚配合VS2017使用完全可以胜任生产环境。最后再分享一个我在实际项目里用的套路统一封装一个excel_export.c模块把workbook创建、worksheet添加、常用格式初始化、数据写入这些操作全部包成自己的函数。业务代码只管传结构体数组不直接接触libxlsxwriter API。这样将来就算要换库也只需要改这一个模块的代码不用动业务逻辑。这个封装思路比任何花哨的用法都更值得你先做。本文还有配套的精品资源点击获取
返回列表