资讯动态

Linux下Qt读写xls中文路径报错?改一行宏轻松解决

发布时间:2026/10/22 5:34:51 来源:尧图企业网站定制
简介一份面向 QT5 Linux 开发者的 ExcelFormat 第三方库修改版源码重点解决中文路径无法读写 Excel 2003 文件的问题适合正在做国产化适配的 C/QT 工程师参考。原版库在执行 wcstombs 与 mbstowcs 转换时对非英文路径的长度计算有误导致文件打开失败作者通过新增 NON_ENGLISH_PATH_FILENAME_SUPPORT 宏调整 create 和 open 函数中的文件名长度计算方式使中文、日文、韩文路径均能正常读写同时保留表格样式设置功能。压缩包共 21 个文件包含 5 个 cpp 与 5 个 h 的核心源码及对应编译对象其余为 pro 工程文件、ui 界面、Makefile、示例主程序和调试配置资源体积约 2.61MB便于在统信 UOS 的 aarch64、mips64 架构下用 gcc 快速编译。包内 demo 示例清晰展示调用方式也记录了完整的错误原因与修复步骤让使用者不仅能用还能理解背后的字符编码转换原理。目前已有 392 人学习适合需要为 Linux 环境增加 Excel 读写能力的项目直接集成尤其适用于信创场景下办公文件处理模块的快速落地。1. Linux下读写xls的中文路径问题一个改源码才能解决的坑把Qt程序从Windows迁到Linux第一轮编译清理完头文件第二轮就是在服务器端跑数据导入导出。很多Qt开发者第一次接触Linux下的xls读写时第一反应是找QAxObject结果发现这东西依赖COMLinux下根本没有——只能转到纯C的ExcelFormat这类第三方库。可这类库原本是Windows时代写的文件路径转换默认只处理ASCII一旦你给文件起个“月度报表-2024.xls”这种名字读出来的是坏档甚至直接打不开。这个资源包就是针对这个场景改好的源码版核心改动只有一个宏但解决了中文、日文、韩文路径在Linux下的读写问题。适合正在做国产化适配、需要在信创环境的Qt程序里处理老版xls文件的开发者也适合想搞明白wcstombs到底怎么坑了你的运维工程师。2. ExcelFormat库的工作原理BasicExcel怎么把xls当成一个“二进制容器”2.1 BasicExcel的读入流程从文件路径到单元格值ExcelFormat这个第三方库其实分成两层底层是ExcelFormat.cpp/.h负责解析xls这种BIFF二进制格式上层是BasicExcel.cpp/.h封装成“加载一个工作簿、读写单元格”这种简单接口。我们的业务代码通常只跟BasicExcel打交道。读一个xls文件常见做法是这样#include BasicExcel.h using namespace YExcel; bool readXlsWithChinesePath(const char* path) { BasicExcel e; // 打开一个xls文件path可以是含中文的绝对路径 if (!e.Load(path)) { return false; // 加载失败 } // 按工作簿序号取第一个工作表 BasicExcelWorksheet* sheet e.GetWorksheet(0); if (!sheet) { return false; } // 遍历前10行前5列 for (size_t row 0; row 10 row sheet-GetTotalRows(); row) { for (size_t col 0; col 5 col sheet-GetTotalCols(); col) { BasicExcelCell* cell sheet-Cell(row, col); if (cell cell-Type() BasicExcelCell::STRING) { const char* v cell-GetString(); // v就是单元格里的字符串 } } } return true; }这段代码的逻辑很直白Load()负责把整个文件读进内存GetWorksheet(0)拿第一个工作表然后按行列取单元格。Cell(row, col)不会越界如果行列超过已有数据它会自动扩展工作表所以读之前最好用GetTotalRows()和GetTotalCols()限制循环范围。这里有个关键参数Load()接收的是const char*路径。在Windows上这个接口可能走的是ANSI或宽字符转换到了Linux下char*被当成UTF-8字节串而库内部的路径处理函数用的是mbstowcs把多字节转宽字符。原版在这个转换上只按字符数分配缓冲中文路径就截断了。2.2 写表格的四个关键设置样式、合并、列宽、单元格值写xls比读稍微复杂一点因为要控制格式。BasicExcel支持单元格值、合并单元格、列宽和简单样式。下面这段是我在服务器端生成报表的常见写法void writeXlsDemo(const char* path) { BasicExcel e; // 创建新工作簿带一个默认工作表 e.New(1); BasicExcelWorksheet* sheet e.GetWorksheet(0); sheet-Rename(月度数据); // 设置列宽参数是列号和宽度 sheet-SetColWidth(0, 30); // 第一列宽度30个字符 sheet-SetColWidth(1, 40); // 写表头 sheet-Cell(0, 0)-SetString(项目); sheet-Cell(0, 1)-SetString(金额); // 写一行数据 sheet-Cell(1, 0)-SetString(服务器采购); sheet-Cell(1, 1)-SetDouble(12980.50); // 合并第一行前两列作大标题 sheet-MergeCells(0, 0, 0, 1); // (行起, 列起, 行止, 列止) // 用BasicExcel::Save写出文件 if (!e.Save(path)) { // 失败时多半是路径或文件权限问题 } }New(1)创建一个含一个空工作表的工作簿。SetCell(row, col)返回单元格指针可以连续调用SetString、SetDouble、SetInt。MergeCells的四个参数分别是起始行、起始列、结束行、结束列注意是闭区间所以MergeCells(0,0,0,1)合并的是第1行第1到第2列。列宽的单位是字符数不需要精确像素够用。样式这块原库提供的接口非常古老没有Windows下那种FontColor的全面封装只能设置对齐方式或者边框。改版包里带的demo也主要是验证读写样式不是重点。如果你需要复杂的字体、颜色、边框建议直接用ExcelFormat里更底层的接口但不推荐维护成本太高。2.3 为什么第三方库比QAxObject更适合Linux服务器这是很多初学者会问的问题。QAxObject是Qt自带的ActiveX容器在Windows上可以直接调用Excel COM接口写出来代码像这样QAxObject* excel new QAxObject(Excel.Application);但COM在Linux上没有原生实现你只能在wine下跑风险极高。而ExcelFormat是纯C解析BIFF二进制不依赖外部程序光这一点就决定了它在服务器端的可用性。服务器上通常没有图形界面、没有office套件运维环境也不允许装乱七八糟的兼容层纯解析库是最稳的。代价是它对xlsx2007之后的格式支持很差只擅长老式的xls。如果你的业务只需要xlsExcelFormat修改版就是性价比最高的方案。这个资源里还带了moc_mainwindow.cpp、mainwindow.ui和完整的Qt工程文件说明开发者是在Qt Creator里建的界面demoUI层只是用来展示读到的表格内容核心逻辑都在BasicExcel里。所以拿到包之后直接把BasicExcel.cpp、ExcelFormat.cpp和对应头文件拷到你的非界面工程里用即可UI部分可以丢掉。3. 修改版的核心改动加一个宏解决多字节路径转换3.1 原版崩溃的根因wcstombs和mbstowcs的宽字符长度计算原版在打开文件和创建文件时内部要把const char*类型的路径转成wchar_t*用到的两个函数是mbstowcs和wcstombs。前者把多字节字符串转成宽字符数组后者反过来。常规用法是先用NULL调用一次拿到目标缓冲长度再分配空间size_t len mbstowcs(nullptr, src, 0); // 获取需要多少wchar_t wchar_t* wbuf new wchar_t[len 1]; mbstowcs(wbuf, src, len 1);原版为了性能没有做两次调用而是直接假设“字符数 字节数”所以它这样写size_t filenameLength wcslen(filename); // filename是wchar_t*如果路径是纯英文wcslen返回的字符数刚好等于需要的wchar_t个数。但路径里有中文、日文、韩文时wcslen只数出了宽字符个数没有考虑每个宽字符转换到UTF-8多字节串后需要更多存储空间。结果就是转换目的缓冲区开小了导致路径被截断或者转换后的字节序列不完整fopen直接失败。这就是原库“不能支持中文路径”的真正原因不是编码映射问题是内存大小算错的问题。3.2 改动点逐行对照create和open中的filenameLength修改版的做法非常直接在ExcelFormat.cpp的create和open两个函数里把原本统一的长度计算改成条件判断#ifdef NON_ENGLISH_PATH_FILENAME_SUPPORT size_t filenameLength wcslen(filename) * 2; #else size_t filenameLength wcslen(filename); #endif这段代码的作用是当你定义了NON_ENGLISH_PATH_FILENAME_SUPPORT这个宏就用宽字符个数乘以2作为目标缓冲长度不定义就走原逻辑。为什么乘以2因为在Linux下一个wchar_t是4字节但UTF-8编码一个汉字需要3个字节乘以2是给出足够余量保证目标缓冲不会截断。日文、韩文也是多字节同样适用。同时在头文件里要加上这个宏定义#ifndef NON_ENGLISH_PATH_FILENAME_SUPPORT #define NON_ENGLISH_PATH_FILENAME_SUPPORT #endif注意这段宏要放在被包含的头文件里或者放到所有源文件都能看到的位置。我习惯单独放在ExcelFormat.h顶部这样ExcelFormat.cpp编译时已经能看到宏不需要额外改编译器参数。3.3 编译宏的作用范围和副作用这个宏不只是影响路径长度计算只要代码里所有跟文件名多字节转换相关的地方都会被激活。所以改版包里的BasicExcel类和ExcelFormat类的路径处理是一起生效的。副作用也有缓冲区长了一倍内存开销变大但一个文件名顶多几百字节这点可以忽略。真正需要留意的是这个宏只解决了“路径转换后存放空间不足”的问题没有解决“源字符串的编码格式”问题。也就是说你传进来的const char* path必须是当前系统区域设置一致的编码。在Linux的Qt工程里如果你从QString::toLocal8Bit()拿路径那就没问题如果QString::toUtf8()某些系统上也可能对但不是绝对。最稳的做法是用QFile::encodeName()这个函数专门为本地文件系统编码设计。4. 在Linux下编译和运行demogcc、qmake和Makefile的完整过程4.1 环境准备与qmake配置aarch64/mips64这个包测试通过的处理器架构是aarch64和mips64操作系统是某国产信创系统。不管哪种架构只要Qt5和gcc可用编译流程都一样。先看工程文件。资源里提供了excelformat.pro内容类似QT core widgets TARGET excelformat TEMPLATE app SOURCES \ main.cpp \ mainwindow.cpp \ BasicExcel.cpp \ ExcelFormat.cpp HEADERS \ mainwindow.h \ BasicExcel.h \ ExcelFormat.h FORMS mainwindow.ui这里要注意如果工程里使用了Q_OBJECT宏mainwindow.h里有HEADERS必须包含对应头文件否则moc不会生成moc_mainwindow.cpp链接时会报一堆undefined reference to vtable。资源包里已经有编译生成的moc_mainwindow.cpp和moc_mainwindow.o说明这步已经验证过但你自己重新编译时最好清理掉旧文件让qmake重新生成避免架构不匹配。4.2 编译命令和参数说明在终端里这样走# 清理之前可能存在的构建产物 make distclean # 用qmake生成Makefile qmake excelformat.pro # 编译-j4用4个线程 make -j4如果qmake不在PATH里需要指定全路径常见位置是/usr/lib/qt5/bin/qmake。在某些信创系统上默认的gcc可能比较老如果碰到C11相关的错误可以给.qmake.stash和Makefile删掉然后在.pro里加一行CONFIG c11qmake生成Makefile后编译出来的可执行文件叫excelformat。如果只是要用库可以不用跑demo直接把BasicExcel.h、BasicExcel.cpp、ExcelFormat.h、ExcelFormat.cpp四个文件拷进你自己的工程然后在源码里加#define NON_ENGLISH_PATH_FILENAME_SUPPORT。4.3 demo代码怎么用一个写入然后读回的例子demo的mainwindow只是一个点击“导入”“导出”按钮的壳子核心逻辑在调用代码里。我习惯按下面的思路做验证#include QCoreApplication #include QDir #include BasicExcel.h using namespace YExcel; int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); // 构造一个含中文路径的测试文件 QString dir QDir::currentPath() QString::fromUtf8(/测试目录); QDir().mkpath(dir); QString filePath dir QString::fromUtf8(/数据表.xls); // 写文件 { BasicExcel e; e.New(1); auto sheet e.GetWorksheet(0); sheet-Cell(0, 0)-SetString(名称); sheet-Cell(0, 1)-SetString(地址); sheet-Cell(1, 0)-SetString(某机房); sheet-Cell(1, 1)-SetString(/data/备份); // 关键用QFile::encodeName转成系统文件名字节串 QByteArray pathBytes QFile::encodeName(filePath); e.Save(pathBytes.constData()); } // 读回并校验 { BasicExcel e; QByteArray pathBytes QFile::encodeName(filePath); if (!e.Load(pathBytes.constData())) { return 1; } auto sheet e.GetWorksheet(0); const char* val sheet-Cell(1, 0)-GetString(); // 这里val应该等于某机房 } return 0; }这里最容易被忽略的是QFile::encodeName()。在绝大多数Linux系统的Qt应用里QString内部是Unicode但传给BasicExcel这种用char*的库时必须转成“本地文件系统编码”。Qt官方的QFile::encodeName会按系统配置处理比toLocal8Bit()更可靠。如果你在服务器上发现中文文件名读写偶尔失败先检查是不是用了toUtf8()传路径。编译时如果这个验证工程和demo放一起需要保证.pro里没有多余的东西QT - gui QT core CONFIG console c11 SOURCES test_main.cpp BasicExcel.cpp ExcelFormat.cpp HEADERS BasicExcel.h ExcelFormat.h之所以要QT - gui是因为命令行验证不需要界面链接更轻。这里用了QT core而不是widgets毕竟我们只是用QCoreApplication和QFile。5. 避坑记录中文路径、编码、架构与第三方库的四个常见问题5.1 现象编译报错undefined reference to vtable在别人的机器上使用这个包时经常一make就报一堆虚函数表未定义。原因是mainwindow.h里声明了Q_OBJECT但原工程清理后没有重新跑moc。虽然包里带着moc_mainwindow.cpp那是构建中间产物不具备可移植性。解决删除.qmake.stash、Makefile以及所有moc_*.cpp然后重新执行qmake和make。qmake会扫描HEADERS里的头文件自动生成正确的moc文件。另外如果是把源码移植到自己的工程别忘了把mainwindow.h加进HEADERS否则moc同样不跑。5.2 现象路径改成中文后能写不能读使用修改版时有人发现创建新文件可以成功但再次Load同一个文件却失败。多半是只改了create函数没改open函数。原版里这两处都有filenameLength计算必须同步加上宏判断。检查方式是全文搜索filenameLength wcslen(filename)找到几处就要改几处一个都不能漏。5.3 现象写成xls后用Excel打开中文乱码如果只在Linux下读写用这个库没问题。但把生成的xls拷回Windows用Office打开中文字段可能显示乱码。原因很多一是文件名正常但单元格内字符串是UTF-8字节而xls内部某些字符串记录需要按代码页存储ExcelFormat老版本默认按ANSI处理二是单元格字符串用了QString::toUtf8()传进去库内部又做了一次字符转换双重转换就炸了。解决思路写单元格内容时统一使用本地方言编码转换比如QString::toLocal8Bit().constData()。如果依旧乱码可以用QTextCodec把字符串转成GB18030再传因为老xls在中文Windows下的默认代码页更接近GBK。这个库源码里没有内置编码映射表所以需要谁用谁处理。5.4 现象mips64架构上编译报错sizes of pointer and int differ在mips64这种64位架构上旧代码里可能有int保存指针的隐患。ExcelFormat某些版本里用DWORD存偏移量在64位下没问题但有些环境变量用int就错了。报错信息一般是某个结构体成员是int你却把它赋值给指针。解决不要用发行版自带的旧源码用这个修改版里的ExcelFormat.h/.cpp因为已经按64位环境调整过。假如你自己引入新版本编译时加-Wpointer-to-int-cast看看具体位置手动把int改成INT_PTR或size_t。5.5 现象运行时崩溃在GetCell返回空指针BasicExcel的Cell(row,col)在自动扩表之后返回指针但有些调用方式在“取不存在的单元格”时返回的不是空指针而是内部未初始化的容器对象。野指针访问时直接段错误。解决访问前先判断sheet和cell是否为null别在空值上直接调GetString。尤其要注意Cell传入的行列值不要超过GetTotalRows()-1和GetTotalCols()-1因为MergeCells会改变行列布局但早期索引仍然有效。6. 验证修改是否生效做一个覆盖中文路径的回归测试拿到这个资源之后别急着往业务代码里塞先花十分钟做个自动化回归。我的做法是写一个独立的测试shell脚本专门造一批中文/日文/韩文路径的xls文件读写后校验内容一致性。测试脚本可以这样设计生成一个包含常用中文、生僻字、日文平假名、韩文谚文的文件名列表然后循环执行同一个编译好的读写测试程序。程序内部会对每个路径写入一个带特征值的单元格再读回来比对。一旦某个路径失败脚本立刻打印文件名和失败的字符位置。#!/bin/bash # 回归测试中文路径xls读写 test_bin./test_excelformat result_dir./结果目录 mkdir -p $result_dir while IFS read -r name; do file$result_dir/${name}.xls if $test_bin write $file; then if $test_bin check $file; then echo PASS: $name else echo FAIL_CHECK: $name fi else echo FAIL_WRITE: $name fi done EOF 月度数据 预算-2024 日本语テスト 한국어시험 中文·标点测试 EOF这个脚本里的test_excelformat是一个简单的Qt控制台程序接收write或check参数。write时创建xls并写入固定字符串“某公司服务器”check时Load同样路径并比对字符串。如果脚本输出所有PASS说明修改版的宏覆盖了所有路径处理分支。第二个验证点是符号链接和相对路径。把测试文件放在一个带中文名的目录下然后用cd进入目录用相对路径调用读写程序。因为wcstombs转换的是传入的路径字符串本身相对路径一样会触发问题。我当年遇到过绝对路径正常、相对路径失败的情况追查下来是调用方把相对路径和当前工作目录拼接后传给了库拼接部分自己转了一次编码库内部又转一次两次不一致导致失败。所以测试用例里一定要加入cd $result_dir ../test_excelformat write 子目录/相对路径测试.xls从那以后我每次拿到Linux下的excel处理库第一件事就是检查它的wcstombs/mbstowcs调用有没有按字节数分配缓冲然后强制跑一遍中文路径回归。习惯成自然这个动作能帮你省掉上线后半夜被叫起来的麻烦。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑