ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

C++用libxlsxwriter在Excel中插入图片的完整实践指南

2026/10/2 17:16:13 拓冰建站 浏览量
C++用libxlsxwriter在Excel中插入图片的完整实践指南 做C桌面程序这么多年我遇到最多的一个“看似简单但网上资料稀少”的需求就是怎么在生成的Excel文件里插入图片。写单元格数据、写公式、设置样式libxlsxwriter或者OpenXLSX都能轻松搞定但一涉及图片很多人的第一反应是回去用COM调用Excel或者干脆放弃直接在Excel里手动贴图。这篇文章我完整记录一下自己用C配合libxlsxwriter库在Excel表格中插入图片的整个方案开发环境是CMake VS2019。整个流程跑通之后生成带产品图、截图、图表的Excel报表也就是几分钟的事。如果你也是C开发者或者正在被“上位机要导出带照片的检测报告”这件事折磨那这篇文章应该能直接帮你少走不少弯路。1. 为什么用libxlsxwriter而不是COM自动化这个问题我最早也想得简单反正Windows上Excel遍地都是程序里创建一个Excel实例往单元格里塞图片不就行了真正做了才发现没那么美好。1.1 这个需求到底在解决什么问题先说场景。我手上接触最多的是设备检测类的上位机程序设备拍完照要导出一张带检测结果的Excel报表里面必须包含现场照片。有的业务系统要批量导商品目录每个商品一行右边放商品图片。还有一些做数据中台的兄弟需要把自动生成的条形码、二维码图片塞进库存表。C里操作Excel的方案不少但真正能把图片作为对象写入xlsx文件的而且不依赖Office环境的其实就这么几个。最典型的坑是用COM方式调用Excel.Application客户机器必须装了Office现场工控机为了省资源经常是精简系统根本没有Excel就算有COM进程一旦异常后台残留一堆EXCEL.EXE进程杀都杀不干净。这种方案用在个人电脑上还行一旦要部署到现场问题就层出不穷。所以“生成xlsx文件”这件事本质上不适合让程序去操作Office应用而应该用开源库直接生成原始文件。Excel的xlsx格式说到底是一个zip容器里面装着若干XML和资源文件。只要按规范把内容写进去Office、WPS、LibreOffice都能正常打开根本不需要办公室软件参与。这就是我最终选择libxlsxwriter这类库的原因。1.2 常见方案的对照与选型市面上能写xlsx的C/C库我做了一个横向对比。直接说结论如果你的目标是“生成新文件、带图片、带图表、带公式”libxlsxwriter是最合适的。方案是否依赖Office图片支持维护成本适用场景COM/OLE自动化依赖可以代码繁琐高仅本地开发不推荐生产libxlsxwriter不依赖原生支持API丰富低C/C后端报表、批量导出OpenXLSX不依赖支持读和写图片能力较弱中需要读取已有xlsx的项目SimpleXlsxWriter不依赖图片支持有限中简单的表格生成手工改zipXML不依赖能做工作量极大极高学习原理可以生产不推荐我特意不用COM方案还有一层考虑性能。COM方式每写一行数据都要跨进程调度图片稍微多一点操作速度肉眼可见地慢。libxlsxwriter底层是纯C实现压缩流程走zlib几千行数据加几十张图生成时间基本可以忽略。如果你的程序还要部署到Linux服务器上做批量报表COM方案直接就出局了libxlsxwriter天然跨平台Windows、Linux、macOS都能编。1.3 环境选型VS2019、CMake与zlib开发环境为什么是CMake VS2019这个倒不是硬性要求而是绝大多数C上位机项目都是这个组合。VS2019自带了对CMake的完整支持可以直接把CMakeLists.txt所在目录作为文件夹打开免去手动生成.sln再导工程的麻烦。VS2019的MSVC编译器对应的是v142工具集这个对libxlsxwriter来说完全够用。唯一要注意的是zlib依赖。xlsx本质是ziplibxlsxwriter用了zlib做压缩Windows上没有系统自带的zlib所以编译或者链接的时候需要额外处理。我后面第2章会详细讲两种处理方式一种是用vcpkg一条命令装好另一种是自己下载源码编译。建议新手直接用vcpkg省心不会在zlib上浪费半天时间。2. 三步准备开发环境环境准备不复杂但这一步做不好后面全是链接错误和找不到头文件的报错。我按自己实际操作过程走一遍。2.1 准备libxlsxwriter库vcpkg与源码编译两种方式第一种方式用vcpkg安装。前提是你先装好vcpkg然后执行git clone https://github.com/microsoft/vcpkg.git cd vcpkg bootstrap-vcpkg.bat vcpkg install libxlsxwriter:x64-windows vcpkg integrate install注意这里指定了x64-windows是因为我的目标平台是64位。vcpkg会自动把libxlsxwriter和它的zlib依赖一起编译出来装完以后在CMake里通过find_package就能找到。这也是我推荐的方式。第二种方式源码编译libxlsxwriter。如果你不想引入vcpkg或者需要在离线环境工作那就自己下载源码。大概步骤是git clone https://github.com/jmcnamara/libxlsxwriter.git cd libxlsxwriter cmake -S . -B build -G Visual Studio 16 2019 -A x64 -DZLIB_ROOTD:/vcpkg/installed/x64-windows cmake --build build --config Release这里我用vcpkg装的zlib作为ZLIB_ROOT你也可以单独下载zlib源码自己编译。编译完成以后生成的xlsxwriter.lib在build/lib/Release目录下头文件在include目录下。后面CMakeLists.txt里手动指定这两个位置就可以。说实话源码编译也不复杂但前提是zlib路径别搞错。我第一次搞的时候没指定ZLIB_ROOT结果CMake缓存都生成不了一直报找不到zlib.h。这个坑我放到第5章细说。2.2 用VS2019打开CMake工程F5直接调试VS2019对CMake项目的支持已经非常成熟。配置好CMakeLists.txt之后最省事的方式是在VS2019里选择“文件” - “打开” - “文件夹”选中项目根目录。VS会自动检测根目录下的CMakeLists.txt并开始生成CMake缓存。缓存生成成功后顶部工具栏的“启动项”会多出一个exe选项选择你的目标程序。直接按F5就能像普通VS项目一样打断点调试。这里有一个小建议VS2019自带的CMake版本可能不是最新如果你的CMakeLists.txt里用了比较新的语法或者libxlsxwriter的配置文件要求CMake版本更新可以在“管理配置”里指定CMake路径或者单独下载安装一个新版CMake然后在VS里指定使用外部CMake。如果你更习惯传统的.sln工作流也可以用命令行先生成cmake -S . -B build -G Visual Studio 16 2019 -A x64然后打开build目录下的ExcelImageDemo.sln来编译。两种方式没有本质区别只是个人习惯偏好问题。我平时调试CMake工程直接用“打开文件夹”模式编译调试效率更高。2.3 最小CMakeLists.txt配置模板下面这份配置是我项目里在用的放在工程根目录。关键点在于MSVC编译器要加/utf-8参数不然源码里的英文字符串没问题一旦写中文就会乱码甚至导致编译警告。cmake_minimum_required(VERSION 3.16) project(ExcelImageDemo LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) if(MSVC) add_compile_options(/utf-8) endif() find_package(libxlsxwriter CONFIG REQUIRED) add_executable(excel_image_demo main.cpp) target_link_libraries(excel_image_demo PRIVATE xlsxwriter)这段配置的逻辑很直接找libxlsxwriter包然后把target链接到xlsxwriter库上。如果你是用源码编译方式不通过vcpkg安装find_package可能会找不到库这时候要把第2.1节里的头文件和库路径手动写进去set(LIBXLSXWRITER_INCLUDE_DIR D:/libs/libxlsxwriter/include) set(LIBXLSXWRITER_LIB D:/libs/libxlsxwriter/build/lib/Release/xlsxwriter.lib) target_include_directories(excel_image_demo PRIVATE ${LIBXLSXWRITER_INCLUDE_DIR}) target_link_libraries(excel_image_demo PRIVATE ${LIBXLSXWRITER_LIB})这样配置完工程基本就通了。接下来才开始写核心代码。3. 插入图片的核心API与坐标计算libxlsxwriter的API设计比较贴近Excel对象模型先创建workbook再添加worksheet然后往worksheet里写数据和图片最后close。理解了这条主线后面所有操作都是围绕它展开的。3.1 libxlsxwriter写入表格的基本流程最标准的流程是这样的#include xlsxwriter.h lxw_workbook *workbook workbook_new(demo.xlsx); lxw_worksheet *worksheet workbook_add_worksheet(workbook, Sheet1); worksheet_write_string(worksheet, 0, 0, 你好Excel, NULL); worksheet_write_number(worksheet, 0, 1, 100, NULL); workbook_close(workbook);看着简单但底层做了不少事情生成worksheet的XML、维护sharedStrings字符串表、把多个XML按规范打包成zip。workbook_close这个动作尤其重要它在关闭文件时把所有内存中的数据统一落盘生成完整的xlsx。我个人的习惯是每一步API都检查返回值。libxlsxwriter绝大多数API都会返回lxw_error枚举LXW_NO_ERROR表示成功。不要嫌麻烦尤其是图片插入这种I/O操作一旦图片文件不存在或者格式不对返回的错误码能帮你快速定位问题。3.2 四种插入图片API怎么选libxlsxwriter提供了好几个图片插入接口它们在底层做的事情不同使用场景也不同。我列了一个对照表API功能适用场景worksheet_insert_image按行列位置插入图片文件快速验证图片不需要偏移/缩放worksheet_insert_image_opt在指定位置插入支持偏移、缩放、超链接等绝大多数实际项目worksheet_insert_image_buffer从内存buffer插入图片图片来自网络或数据库不落盘worksheet_embed_image嵌入图片的新接口需要把图片嵌入到工作簿的场景我日常用得最多的是worksheet_insert_image_opt。它比基础版多了一个lxw_image_options结构体参数可以精细控制图片的位置和外观。如果只是临时测试可以直接用worksheet_insert_image一行代码搞定。buffer系列接口则非常适合在线下载图片后直接写入的场景可以避免临时文件被误删的尴尬。3.3 图片位置、缩放和对象行为的参数细节lxw_image_options结构体里真正影响布局的核心字段有这几个字段类型作用x_offset / y_offsetuint32_t相对于单元格左上角的偏移量单位像素x_scale / y_scaledouble图片缩放比例1.0为原始大小object_positionuint8_t图片随单元格移动/改变大小的行为descriptionchar*图片Alt文本无障碍访问用urlchar*点击图片跳转的超链接object_position有四个枚举值理解它们对生成符合预期的报表很重要枚举值含义LXW_OBJECT_MOVE_AND_SIZE图片随单元格移动和缩放类似Excel手动插入图片后选择“随单元格移动和调整”LXW_OBJECT_MOVE_DONT_SIZE图片随单元格移动但大小不变LXW_OBJECT_DONT_MOVE_DONT_SIZE图片既不移也不变固定在绝对位置LXW_OBJECT_MOVE_AND_SIZE_AFTER图片在单元格排序后再重新锚定通常用于插入后调整这玩意儿刚开始容易忽略但如果你的Excel文件后续会被别人修改比如插入行、调整列宽object_position设置不对图片就会乱跑。我的建议是如果希望图片跟行记录绑定用MOVE_AND_SIZE如果只是想当个水印用DONT_MOVE_DONT_SIZE。还有一点必须注意x_scale和y_scale要同时设置单独设置一个会让图片变形。如果你想缩到50%就两个都写0.5。3.4 把图片精确“排”在表格里的坐标计算方法这是很多人卡壳的地方。Excel界面里看到的是行列但DrawingML锚点定位用的是像素。为什么我插一张图明明指定了row1, col1图片却对不齐因为Excel默认行高15磅约等于20像素默认列宽8.43个字符约等于64像素。图片如果比单元格大就会溢出到旁边的单元格视觉上很像“没定位准”。解决思路是先明确设置单元格的像素宽高再计算图片的偏移量。我建议用worksheet_set_row_pixels和worksheet_set_column_pixels这两个API直接把行高列宽设置成像素值省去单位换算的麻烦。举个例子我想让B列宽120像素第2行高80像素图片统一缩放为48x48并居中。代码可以这样写worksheet_set_column_pixels(worksheet, 1, 1, 120, NULL); worksheet_set_row_pixels(worksheet, 1, 80, NULL); lxw_image_options options {0}; options.x_scale 0.5; options.y_scale 0.5; options.x_offset (120 - 48) / 2; options.y_offset (80 - 48) / 2; worksheet_insert_image_opt(worksheet, 1, 1, product.png, options);这里的关键在于图片缩放到48像素后水平居中的偏移量就是(列宽像素 - 图片像素)除以2。垂直方向同理。这套方法非常朴素但能解决绝大多数“图片排不齐”的问题。需要注意的是如果图片原始大小不是96x96直接把x_scale设成0.5就不对必须拿图片实际宽高来算。读取PNG实际尺寸的方法我在第4章的完整代码里给了。4. 完整示例生成带商品图片的Excel报表理论部分说清楚了下面上一个可以直接跑通的完整例子。我以一个商品列表报表作为演示生成结果是一个带表头、带商品名、带图片、带价格的xlsx文件。4.1 示例需求与数据结构示例需求是这样表格第一行是表头分别是商品名称、商品图片、价格。下面三行是三个商品。图片统一放在assets目录下命名是apple.png、banana.png、cherry.png。价格分别是12.5、5.8、29.9。为了让图片显示得整齐我把商品图片列设置为120像素宽行高设置为80像素图片缩放到宽48像素高度按原图比例缩放然后在单元格内居中显示。这正好用上了第3章说的坐标计算方法。4.2 完整可运行的main.cpp与CMakeLists.txt先给CMakeLists.txt在2.3节的基础上加了一个图片资源的复制步骤方便调试时直接拿到图片路径。如果你把图片放在工程目录下的assets文件夹那就没那么复杂直接用相对路径就行。cmake_minimum_required(VERSION 3.16) project(ExcelImageDemo LANGUAGES C CXX) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) if(MSVC) add_compile_options(/utf-8) endif() find_package(libxlsxwriter CONFIG REQUIRED) add_executable(excel_image_demo main.cpp) target_link_libraries(excel_image_demo PRIVATE xlsxwriter)然后是main.cpp。我在代码里加了一个简单的PNG尺寸读取函数用最原始的方式解析PNG头部信息。这个函数不依赖第三方库读取PNG文件的前24个字节就能拿到宽高。原理是PNG文件在8字节签名之后有IHDR块宽度和高度各占4字节以大端存储。#include xlsxwriter.h #include cstdio #include cstring bool get_png_size(const char *filename, int *width, int *height) { FILE *fp fopen(filename, rb); if (!fp) return false; unsigned char buf[24]; if (fread(buf, 1, 24, fp) ! 24) { fclose(fp); return false; } fclose(fp); const unsigned char png_signature[8] {137, 80, 78, 71, 13, 10, 26, 10}; if (memcmp(buf, png_signature, 8) ! 0) return false; *width (buf[16] 24) | (buf[17] 16) | (buf[18] 8) | buf[19]; *height (buf[20] 24) | (buf[21] 16) | (buf[22] 8) | buf[23]; return true; } int main() { lxw_workbook *workbook workbook_new(goods_report.xlsx); lxw_worksheet *worksheet workbook_add_worksheet(workbook, 商品报表); lxw_format *header_format workbook_add_format(workbook); format_set_bold(header_format); format_set_align(header_format, LXW_ALIGN_CENTER); format_set_bg_color(header_format, 0xD9E1F2); worksheet_write_string(worksheet, 0, 0, 商品名称, header_format); worksheet_write_string(worksheet, 0, 1, 商品图片, header_format); worksheet_write_string(worksheet, 0, 2, 价格, header_format); const char *names[] {青苹果, 香蕉, 樱桃}; const char *files[] {assets/apple.png, assets/banana.png, assets/cherry.png}; double prices[] {12.5, 5.8, 29.9}; int col_width 120; int row_height 80; int target_width 48; worksheet_set_column_pixels(worksheet, 0, 0, 100, NULL); worksheet_set_column_pixels(worksheet, 1, 1, col_width, NULL); worksheet_set_column_pixels(worksheet, 2, 2, 80, NULL); for (int row 1; row 3; row) { worksheet_set_row_pixels(worksheet, row, row_height, NULL); worksheet_write_string(worksheet, row, 0, names[row - 1], NULL); worksheet_write_number(worksheet, row, 2, prices[row - 1], NULL); int img_width 0; int img_height 0; if (!get_png_size(files[row - 1], img_width, img_height)) { fprintf(stderr, 无法读取图片尺寸: %s\n, files[row - 1]); continue; } double scale (double)target_width / img_width; int target_height (int)(img_height * scale 0.5); lxw_image_options options {0}; options.x_scale scale; options.y_scale scale; options.x_offset (col_width - target_width) / 2; options.y_offset (row_height - target_height) / 2; options.object_position LXW_OBJECT_MOVE_AND_SIZE; options.description names[row - 1]; lxw_error err worksheet_insert_image_opt(worksheet, row, 1, files[row - 1], options); if (err ! LXW_NO_ERROR) { fprintf(stderr, 插入图片失败: %s, error%d\n, files[row - 1], err); return 1; } } lxw_error err workbook_close(workbook); if (err ! LXW_NO_ERROR) { fprintf(stderr, 保存文件失败: error%d\n, err); return 1; } printf(生成 goods_report.xlsx 成功\n); return 0; }这段代码有两个我特意保留的细节。一个是读取图片尺寸后动态计算缩放比例避免写死宽度导致图片变形另一个是在loop里检查每个API的返回值图片丢失时立刻退出。实际生产环境里图片文件缺失是常事这种防御式写法规避了很多“报表生成一半才发现没图片”的问题。4.3 运行结果验证文件结构、Excel打开效果程序运行后项目根目录下会生成goods_report.xlsx。用Excel打开后三行商品带三张图片图片在单元格里水平垂直居中整体效果比想象中整齐很多。如果你不放心生成的文件或者Excel打开报错可以用压缩软件直接把xlsx解压开看看里面有什么。xlsx本质是zip图片实际放在xl/media目录下。正常情况三张图片对应image1.png、image2.png、image3.png。图片的锚点信息在xl/drawings/drawing1.xml里里面会有oneCellAnchor或者twoCellAnchor标签。这个自查手段很重要很多疑难杂症都靠它定位。另外提醒一句图片是浮动在表格上方的它不会截断单元格里的字符串数据。如果某个单元格写了很长的文本图片正好盖住它只是显示上被遮挡数据本身没有丢。这种问题在设计报表列宽时就应该避免比如图片列单列存放不要和数据文本放在同一个格子。5. 常见问题与排查技巧实录这部分是我自己踩过几个坑之后总结出来的基本覆盖了Windows VS2019场景下最常见的报错和诡异现象。5.1 中文乱码、路径分隔符这些Windows“特色坑”Windows上中文字符串是重灾区。libxlsxwriter内部统一使用UTF-8编码而VS2019的源码文件默认可能是GBK编码编译器在读取源代码时如果不一致处理中文就会出问题。解决办法有两步CMakeLists.txt里加/utf-8编译选项同时源码文件用UTF-8带签名格式保存。两条都做到位中文基本不会乱码。另一个是路径分隔符。Windows习惯用反斜杠但C/C字符串里反斜杠是转义符写路径时要么倒两个反斜杠要么直接用正斜杠。libxlsxwriter在Windows上是支持正斜杠的所以我的习惯是uniformly用正斜杠省得转义出错。如果图片路径或导出文件名包含中文需要确保传入的字符串是UTF-8编码。用std::filesystem的u8path可以方便地处理这类路径但要注意不同C标准下返回类型不一样别在细节上栽跟头。5.2 链接错误zlib、x64/x86、库版本不匹配最常见的报错是“无法打开文件zlib.lib”或者“unresolved external symbol inflate”。这基本就是zlib没有正确链接导致的。vcpkg安装libxlsxwriter时会自动带zlibCMake里find_package也能找到问题不大。源码编译方式就特别容易漏因为你还要单独把zlib编译出来并确保ZLIB_ROOT指向正确的路径。第二个常见问题是平台不一致。用x64的libxlsxwriter静态库却被x86的exe工程链接会提示LNK2001之类的符号错误。检查方法很简单确认VS的解决方案配置是x64CMake命令行里用了-A x64vcpkg triplet也是x64-windows。三者必须一致。第三个问题是运行库不一致。MSVC编译默认有四种运行库组合Debug/Release、/MD与/MT。如果libxlsxwriter是Release /MT编译的你的程序用Release /MD链接也可能出现奇怪的链接错误。vcpkg一般会编译多个版本但如果你是手动指定路径就要特别小心。我建议全链路统一使用Release x64 /MD的配置。5.3 图片不显示、Excel报错的排查顺序Excel打开文件时提示“发现不可读取的内容”这是最吓人的报错。遇到这类问题不要慌按顺序排查图片文件本身是否完好能不能用看图软件打开。图片格式是否被支持。libxlsxwriter支持PNG、JPEG、BMP其他格式比如WebP、GIF传进去不能保证Excel能正常显示。图片路径是否正确。相对路径是相对于程序的工作目录而不是exe所在目录调试器里工作目录经常和项目目录不一致。解压xlsx自查。先看xl/media目录下有没有图片文件。如果一张都没有说明图片API根本没执行成功检查返回值和路径。如果图片在drawing1.xml也在但还是打不开大概率是锚点坐标异常找出对应的twoCellAnchor清理掉或者重新插入。buffer模式下确认图片数据在workbook_close之前一直有效。libxlsxwriter未必会立即复制buffer如果提前释放内存生成的文件很可能损坏。另外还有一个经验图片尺寸过大也会导致生成的xlsx文件膨胀。最好不要把几MB的高清原图直接往里塞先压缩到适合屏幕显示的尺寸文件体积和生成速度都会明显改善。5.4 一些性能与兼容性建议批量生成上百张图片的报表时我建议在程序里加一个简单的图片预检流程先检查文件是否存在读取尺寸如果尺寸异常或文件为0字节直接跳过并打印告警不要中断整个工作簿的生成。大批量图片场景最忌讳的就是一张图片坏了整包文件生成失败所有数据跟着一起浪费。兼容性方面libxlsxwriter生成的xlsx在Office 2007以上版本都能正常打开。WPS也能兼容但某些细节比如object_position的四种行为不同表格软件的渲染可能有细微差异。如果报表要发给外部客户建议发之前在目标软件里过一眼毕竟它每次都是“所见即所得”不试过不知道。5.5 常见问题速查表问题现象常见原因解决办法中文乱码Excel里中文显示为乱码源码编码与UTF-8不一致加/utf-8编译选项源码存为UTF-8链接失败无法打开zlib.lib或unresolved external symbolzlib未正确链接检查vcpkg或ZLIB_ROOT路径图片不显示Excel里空白图片路径错误或格式不支持检查图片格式与路径解压xlsx自查文件损坏Excel提示发现不可读取的内容图片buffer提前释放或图片损坏确保buffer生命周期用正常图片测试图片错位图片和单元格对不齐没设置列宽行高或offset计算错误用set_column_pixels和set_row_pixels固定尺寸工作目录问题相对路径图片找不到调试器工作目录不是项目目录使用绝对路径或调整工作目录说实话用libxlsxwriter这套方案跑下来整体比我预想的顺手。它不是那种API臃肿的库文档也齐全就是知道的人不算多。我现在所有需要生成带图片Excel的C项目都默认用它。踩过几次坑之后我的习惯是生成完文件马上解压检查一次确认media目录有图、drawing文件有锚点再交付给业务方或继续执行下一个流程。如果你准备在自己的项目里用建议先跑通这篇文章里的最小示例然后逐步加入图表、条件格式、单元格格式这些高级功能。libxlsxwriter的图表API也挺值得研究的我后面打算把“生成带趋势图的报表”也整理一篇把这类C写Excel的实用场景都串起来。