十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

C++读取WPS表格:CSV中转与COM自动化两条路径详解

C++读取WPS表格:CSV中转与COM自动化两条路径详解 简介面向Windows平台C开发者的一份基于VS2010 MFC框架、通过WPS Office API读取XLSX文件的工程源码包用于解决在没有完整Microsoft Excel环境下仍需读取Excel数据的常见需求。压缩包为完整VS工程共29个文件包含12个.h头文件、3个.cpp源文件以及.sln解决方案、.rc界面资源、.vcxproj工程配置等整体约19.6MB解压后可直接用VS2010打开编译。工程以testexcel为示例封装了CApplicationET、CWorkbooks、CWorkbook、CWorksheets、CWorksheet、CRange等核心类分别对应WPS表格应用、工作簿、工作表和单元格区域操作从加载WPS动态库、获取接口函数到打开工作簿、遍历工作表与单元格数据、更新MFC控件再到释放资源形成了一条完整调用链。代码中对Open XML文件结构、动态链接库调用、错误处理与线程安全也有一定示范便于二次开发时直接参考。该资源已有1787人学习下载特别适合中高级C开发者在MFC中集成WPS读取Excel时按需取用若想快速搭建类似读取原型也可直接参考其类设计和调用流程。 “C通过WPS读取Excel”这个句式我太熟悉了——但凡在业务系统、桌面工具、自动化脚本里做过数据导入导出的人多半都被这问题卡过。本意是想让C程序去读一份表格文件结果发现办公电脑上装的是WPS而不是微软Office网上那些Excel.Application的COM代码全都不好使。其实这事没有想象中难关键是把两条路线想清楚一条是彻底绕开表格格式走CSV中转另一条是直接用WPS暴露出COM接口让C像调用Excel对象模型一样操作WPS里的工作表。这篇文章就把这两种方案完整拆开讲从原理到代码再到各种边角坑一次说透。1. 选型之前先把需求想明白1.1 常见C读取Excel方案全景C读取Excel/表格文件市面上一共有这么几条路直接解析文件格式.xls是二进制OLE格式.xlsx是ZIP打包的XML。理论上你可以自己解压、读XML、拼字符串但现实里细节极多单元格样式、共享字符串、日期序列化、合并单元格、公式缓存……工作量堪比写一个小型Office套件。有第三方库比如libxls、OpenXLSX、libxlsxio能省不少事但遇到WPS保存的.et、.ett这类自家格式时支持度就会打折扣。ODBC微软提供过Excel的ODBC驱动可以把.xlsx当数据源查询。听着很美但先决条件是要装驱动、配数据源而且WPS格式支持不好。真用起来配置成本很高报错还极难排查不建议作为首选。中转到CSV让Excel或WPS把表格另存成CSVC程序只负责解析纯文本CSV。简单、可控、跨平台缺点是丢格式、多变多Sheet处理繁琐。COM自动化调用已安装的WPS/Office暴露的COM接口让WPS去干活。既能读也能写还能保留格式、公式、图表是最“正统”的Windows桌面自动化方案。1.2 为什么最终锁定“CSV中转”和“COM自动化”两条路做了这么多项目之后我的结论其实很明确没有银弹只有取舍。如果你的需求是把大量的、结构规整的表格数据导入到自己的软件里而且表格是人工整理好的那CSV中转是性价比最高的路径。数据量再大也就几十MBC解析CSV的速度是毫秒级的问题只出在编码上。如果你的需求是自动操作WPS比如打开某个模板、填数据、另存为PDF或者需要读取多个Sheet里的特定区域那就必须走COM自动化。COM的好处是WPS已经把解析和格式还原的事做完了你的程序只需要告诉它“打开哪个文件、读哪个单元格”就行。这两条路可以共存。我很多项目都是先让WPS把原文件统一转成CSV再让C读CSV这样既避开了COM调度的性能问题又解决了直接解析格式的复杂度。后面几章会给出具体做法。2. 最简单的落地路线CSV中转方案2.1 什么时候值得用CSVCSV方案适合这几类场景数据内容是二维表列名固定行数多不需要保留单元格字体、颜色、合并关系不需要多Sheet一次性读取或者可以每个Sheet分别导出成一个CSV文件程序要跑在多个操作系统上不想被Windows COM绑定。我做过一个MES系统的数据导入模块现场工人每天用WPS填一张Excel表系统需要把表中的物料编码、批次号、数量读进来。这类数据完全用不到格式信息直接在WPS里把表另存为CSV程序解析后入库整条链路从开发到维护都非常省心。2.2 C解析CSV的要点编码、转义、换行CSV看着就是个逗号分隔文本但实际写解析器时要注意三个点编码中文环境下的Excel/WPS另存CSV默认大概率是GBK/GB18030不是UTF-8。如果你的程序内部用std::string处理就需要在读取时转换编码或者在WPS导出CSV时手动选择UTF-8新版WPS会弹编码选项。强烈建议统一走UTF-8否则程序从一个机器搬到另一个机器写中文路径、中文表头都会成为玄学问题。BOM头某些版本的WPS导出UTF-8编码的CSV会在文件开头塞一个0xEF 0xBB 0xBF的BOM解析前必须判断并跳过否则第一列的第一个字段会多出一个看不见的字符。引号转义如果单元格内容本身含逗号、换行或引号CSV规范会要求用双引号包起来内部引号用双引号转义。例如名称,描述 Apple,Its, a test一个最小可用的C解析器长这样#include string #include vector #include fstream #include iostream // 简单解析一行CSV返回按逗号分隔的字段 std::vectorstd::string parseCsvLine(const std::string line) { std::vectorstd::string fields; std::string field; bool inQuotes false; for (size_t i 0; i line.size(); i) { char c line[i]; if (c ) { if (inQuotes i 1 line.size() line[i 1] ) { field ; // 转义的双引号 i; } else { inQuotes !inQuotes; } } else if (c , !inQuotes) { fields.push_back(field); field.clear(); } else { field c; } } fields.push_back(field); return fields; } int main() { std::ifstream in(data.csv); std::string line; while (std::getline(in, line)) { // 丢弃行尾的\r if (!line.empty() line.back() \r) line.pop_back(); auto fields parseCsvLine(line); for (auto f : fields) std::cout [ f ]\n; } return 0; }2.3 批量把xls/et/xlsx转换成CSV“转换”这一步最简单的做法是在WPS里手动另存为。但如果要批量处理几百个文件手动操作会把人逼疯。此时有两个小技巧技巧一写WPS宏VBA批量转CSV。WPS个人版已经支持VBA宏需要安装VBA组件。打开一个文件按AltF11进入宏编辑器执行循环另存为CSV。技巧二用自动化脚本调度WPS。比如用Python的pywinauto控制WPS界面或者直接用下面第三章要讲的COM接口在C程序里循环调用另存为CSV。这两个办法本质上都是“借用WPS的解析能力把格式问题拦在源头”。一旦文件变成了CSV后面C处理起来就非常透明。3. 高级路线用WPS的COM接口在C里直接读写3.1 COM自动化原理与WPS对象模型COMComponent Object Model是Windows的老牌组件标准。WPS安装后会在系统注册表里注册一个COM对象外部程序可以通过CLSID或ProgID创建这个对象获得一个IDispatch接口指针然后调用它的属性、方法来操作WPS。如果你以前用过微软Office的COM编程那过渡到WPS会非常丝滑因为WPS的表格组件对象模型和Excel几乎一致有Application、Workbook/Workbooks、Worksheet/Worksheets、Range、Cells、UsedRange这些对象连方法名都高度相似。唯一的差异在于ProgID。微软Excel通常是Excel.Application而WPS表格一般是KET.Application。网上那些直接写CLSIDFromProgID(LExcel.Application)的代码在只装了WPS的机器上必然失败这也是很多人卡住的头号原因。3.2 环境准备与类型库导入要在C里调用COM通常有两种方式方式一动态获取IDispatch接口手动Invoke调用方法。麻烦但完全不依赖类型库兼容性最好。方式二用#import导入WPS的类型库生成智能指针和包装类。代码写起来像操作本地对象非常省事。缺点是不同版本WPS的类型库路径可能不同需要你根据安装目录去填。我实测项目里用的方式二类型库路径一般是C:\Program Files (x86)\Kingsoft\WPS Office\ksolaunch.exe3.3 核心代码打开表格、读取单元格、遍历Sheet下面这段代码演示了用#importKET.Application读取WPS表格中所有Sheet的A1到C3区域内容。注意这只是示例实际编译时要根据你的WPS安装路径调整#import的路径。#include windows.h #include stdio.h // 根据实际安装路径修改 #import C:\\Program Files (x86)\\Kingsoft\\WPS Office\\ksolaunch.exe no_namespace int main() { CoInitializeEx(nullptr, COINIT_MULTITHREADED); // 1. 创建WPS表格Application对象 CLSID clsid; HRESULT hr CLSIDFromProgID(LKET.Application, clsid); if (FAILED(hr)) { printf(没有找到KET.Application请确认WPS已安装\n); return 1; } IApplicationPtr app; hr app.CreateInstance(clsid); if (FAILED(hr)) { printf(创建WPS对象失败: 0x%08X\n, hr); return 1; } app-Visible TRUE; // 调试时建议设可见正式跑可以设FALSE // 2. 打开工作簿 WorkbooksPtr books app-Workbooks; _WorkbookPtr workbook books-Open(LD:\\test.xlsx, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing, vtMissing); // 3. 遍历所有Sheet WorksheetsPtr sheets workbook-Worksheets; int sheetCount sheets-Count; for (int i 1; i sheetCount; i) { _WorksheetPtr sheet sheets-Item[i]; // 读取某个区域的Value2二维数组 RangePtr range sheet-Range[A1, C3]; _variant_t valueArray range-Value2; // 打印区域内容 VARIANT* pVariantArray valueArray.parray; long row, col; SafeArrayGetLBound(pVariantArray, 1, row); SafeArrayGetUBound(pVariantArray, 1, row); SafeArrayGetLBound(pVariantArray, 2, col); SafeArrayGetUBound(pVariantArray, 2, col); VARIANT element; VariantInit(element); for (int r 1; r 3; r) { for (int c 1; c 3; c) { long indices[] {r, c}; SafeArrayGetElement(pVariantArray, indices, element); if (element.vt VT_BSTR) { printf(Sheet%d: (%d,%d) %ls\n, i, r, c, element.bstrVal); } else { printf(Sheet%d: (%d,%d) [非文本]\n, i, r, c); } VariantClear(element); } } } workbook-Close(FALSE, vtMissing, vtMissing); app-Quit(); CoUninitialize(); return 0; }这段代码有几个关键点#import后使用智能指针IApplicationPtr、WorkbooksPtr这些类型是编译器自动生成的RAII封装能减少大量Release()调用毕竟COM对象的引用计数管理一旦出错就会出现内存泄漏或程序崩溃。Range-Value2一次取一个区域而不是用Cells.Item[r, c]一个格子一个格子去读。后者要跨COM边界上千次速度极慢前者只调用一次拿到一个二维SafeArray效率完全不在一个量级。中文输出BSTR类型宽字符要配合%ls才能正常打印直接用printf(%s)必定乱码。3.4 常见的坑32/64位、ProgID、类型库路径32位 vs 64位WPS个人版默认安装的是32位程序它的COM本地服务器是32位的。如果你的C程序是64位CoCreateInstance会因为位数不匹配而失败报错通常是0x80040154。解决办法很简单C工程编译成x86或者安装64位WPS。我建议工程设置成x86因为很多老驱动、第三方DLL也是32位统一x86能省很多麻烦。ProgID差异有些版本的WPS会注册KWps.Application而不是KET.Application这时可以查注册表确认HKEY_CLASSES_ROOT\KET.Application或者HKEY_CLASSES_ROOT\WPS.Application也可以在注册表里搜索Application前缀找到可用的ProgID。类型库路径不稳定#import的类型库路径如果不对编译阶段就会出错。你可以搜索系统里所有ksolaunch.exe文件或者用regsvr32相关组件自己验证。更稳妥的办法是从HKEY_CLASSES_ROOT\KET.Application\CLSID拿到CLSID然后用GetTypeLibInfo动态加载类型库。不过日常开发中固定路径 安装文档说明是够用的。对象释放顺序关闭工作簿后再Quit()。不要先退WPS再关表格否则容易残留僵尸进程。如果程序崩溃后WPS进程没退出可以在任务管理器里杀掉wps.exe、et.exe进程。4. 从数据结构兼容性看两种方案的取舍4.1 数据类型丢失问题CSV方案最大的痛点是所有单元格在导出后都变成字符串。数字12345和文本12345在CSV里长得一模一样日期2024-05-01导出后也是一个字符串。如果你的程序需要区分数字、日期、文本这三种类型CSV方式就必须靠使用者人工去保证格式统一或者你在导出CSV时额外约定一列“数据类型”字段。COM方案则没有这个问题因为WPS的单元格对象天然携带类型信息VT_BSTR表示字符串VT_R8表示8字节浮点数VT_DATE表示日期VT_BOOL表示布尔值。拿到VARIANT后直接根据vt字段判断类型即可。所以在强数据类型校验的场景下COM是明显更优的。在数据量大、格式宽松的场景下CSV的反而是更好的选择。4.2 大量数据读取时的性能取舍用COM逐个Cells.Item[r, c]读取100万行数据可能会慢到让人怀疑人生——每一次调用都是一次COM方法调度开销极大。正确处理方式是像第三章代码那样用Range-Value2把整块区域一次性取回然后遍历SafeArray。实测下来10万行×10列的数据这种方法大概几百毫秒到一两秒能读完完全够用。CSV则是纯文本解析读100万行也就几秒钟。如果数据量超过几GB那就需要考虑分批读取或换用内存映射文件了不管用哪种方案都一样。4.3 多Sheet与文件名兼容性WPS默认保存的.et格式CSV中转必须先手动转换成CSV而COM方案可以直接用Workbooks-Open(Lxxx.et)打开.et、.xls、.xlsx兼容性最好。这在自动化流程里是一个决定性优势。5. 常见问题与排查技巧实录5.1 问题速查表现象可能原因解决办法创建KET.Application失败HRESULT0x80040154WPS未安装或ProgID不对检查注册表确认ProgID安装WPS创建对象成功但Open打开文件失败路径权限不足或文件正在被占用用绝对路径确保文件未被打开锁定中文乱码编码不一致GBK vs UTF-8CSV按GBK/UTF-8统一转码COM输出用宽字符打开、读取速度极慢逐个单元格访问COM改为一次Range-Value2读取整个区域64位程序无法调用WPS COMWPS是32位COM服务将C工程编译为x86或安装64位WPS程序退出后WPS进程残留对象未正确释放、异常崩溃用智能指针捕获异常后Quit()必要时任务管理器清理读Sheet名称带空格时失败对象参数少了Item[i]的显式转换用sheets-Item[_variant_t((long)i)]获取Sheet读取日期单元格变成数字Excel/WPS日期本来就是序列号用FormatDateTime或根据vt判断日期类型5.2 实操经验与避坑建议先跑通CSV再加COM。如果你刚开始做这个功能先把CSV链路走通能大大降低项目初期的风险。等基本架构稳定了再考虑用COM去处理那些必须要原格式保留的场景。不要一上来就啃COM因为COM涉及Windows平台绑定、WPS版本差异调试成本不低。封装一个统一的“TableReader”接口。无论底层是CSV实现还是COM实现外面一律提供open(path)、get(row, col)、sheetNames()、close()这4个方法。这样以后如果换了表格组件只改实现层业务代码完全不动。调试时把Visible TRUE发布时改成FALSE。我遇到过很多次脚本跑得飞快但结果全错就是因为WPS对象隐藏在后台连报错的弹窗都看不到排查起来非常痛苦。调试阶段把WPS窗口显示出来能直观看到它执行到哪一步、弹了什么错误框。不要在无图形界面的服务器上依赖COM。如果程序跑在Windows Server且没有登录桌面会话COM服务可能无法正常工作。此时老老实实用CSV或者提前在客户端机器转好格式。结尾想说的话实际做这个功能时我最后交出去的版本是一个混合方案用户手工维护的表格在WPS里一键另存为CSV再由后台C程序解析入库自动化报表场景则直接用COM打开模板文件填充数据后另存为PDF或XLSX。两条路各管一摊互不干扰。如果你也想做一个“读表格”的通用能力我的建议是从小处着手先写好CSV解析器再把COM封装一层最后你会发现C读取表格这事其实比想象中干净得多。本文还有配套的精品资源点击获取
返回列表