简介:QuaZip编译好的库lib、dll为Windows平台的C++开发者提供开箱即用的压缩档案处理能力。该库封装了ZIP/RAR常用操作,支持创建、读取、修改ZIP档案及基本的RAR读取功能,适合需要快速集成压缩功能的桌面或Qt项目。资源共29个文件,包含17个头文件、5个CMake配置、4个静态库(lib)、2个动态库(dll)及1个pkg-config文件,覆盖Qt5版本与调试/发布配置,压缩包仅427KB,便于直接引入工程。已有599人学习下载。压缩包内含完整的include目录、预编译库及CMake配置,开发者只需将头文件路径加入包含目录,并在链接器中添加对应lib,即可调用QuaZip接口;运行阶段将对应dll放置于程序目录或PATH即可。随附的示例代码演示了打开ZIP并遍历内部文件列表的方法,可帮助快速验证环境并上手开发,省去自行编译依赖的繁琐步骤。
1. 直接使用QuaZip编译好的lib和dll,比从源码编译省下一个下午
QuaZip编译好的lib和dll,是Qt项目里做ZIP压缩解压最省力的接入方式。它把zlib和minizip那些琐碎的C接口封装成面向Qt的类,压缩目录、解压目录、读取ZIP信息都收敛成几个可以直接调用的方法,配合QString和QFile用起来相当顺手。我最早自己编译QuaZip时,在编译器版本、Debug库后缀、zlib依赖、MinGW与MSVC混用这些细节上反复踩坑,耗进去一个下午还没调通。后来换成直接链接现成的编译产物,配置十分钟就能跑出第一个压缩包。这篇笔记把QuaZip的定位、工程配置、核心API和集成过程中最常见的坑一次讲清,适合接手Qt桌面端或嵌入式项目、需要快速加入压缩解压能力的C++开发者。
2. QuaZip的定位:Qt项目里为什么多数人优先选它
2.1 三个层面的对比:zlib、minizip与QuaZip
zlib是流式压缩底层库,它只处理原始数据流,把一段内存压成另一段内存。ZIP容器结构、目录项、CRC校验这些事,zlib本身一概不管。minizip在zlib之上补齐了ZIP文件解析能力,但接口依然是C函数风格,调用时要自己管理FILE指针、回调结构体,还要在C缓冲区与Qt容器之间来回搬运数据。放在业务代码里,这种写法既容易出错,也会拉低可维护性。
QuaZip的价值在于它专门为Qt做了一层封装。一个QuaZip对象对应一个ZIP包,QuaZipFile对应包里的一个文件条目,JlCompress则把最常用的操作收敛成静态工具方法。底层基于QIODevice,意味着压缩后的数据可以直接接到QBuffer、QFile、信号槽链路上,不用做任何手工缓冲区转换。如果项目本身已经用Qt做界面和文件IO,选QuaZip基本是顺理成章的事。
2.2 lib和dll、静态库与动态库的取舍
编译好的QuaZip以两种形态存在。动态库是一个lib加一个dll的组合,lib是编译链接时用的导入库,dll是运行时需要跟着exe一起分发的动态链接文件。静态库则是单独一个lib,链接后QuaZip的代码会整体编入你的可执行程序。
| 对比项 | 动态库(lib+dll) | 静态库(lib) |
|---|---|---|
| 链接方式 | 导入库配合运行时dll | 代码整体编入exe |
| 独立升级 | 替换dll即可 | 需要重编整个exe |
| 安装体积 | 多两个dll文件 | 无额外dll |
| LGPL义务 | 动态链接更宽松 | 需注意发布时的许可证要求 |
| 调试难度 | 可用工具查看dll依赖 | 只有编译错误可见 |
我一般优先使用动态库。一是版本升级时只需要替换dll,不用重编整个工程;二是动态链接下LGPL的合规约束相对宽松;三是调试阶段可以用依赖查看工具确认dll加载链是否完整。只有在安装包体积控制非常严格,或者目标机器环境完全可控时,我才会考虑静态库方案。
2.3 拿到编译好的库后,先核对三样东西
第一样是Qt版本。Qt5编译出来的QuaZip库拿到Qt6工程里链接,基本必挂,因为内部使用的QString编码、QFile实现和异常机制都发生了改变。第二样是编译器类型。MSVC编译的库只能配MSVC工程,MinGW编译的库只能配MinGW工程,两者混用会出现大量无法解析的外部符号。第三样是zlib依赖。QuaZip本身不实现压缩算法,底层全靠zlib,如果工程里已经存在另一个zlib版本,或者系统PATH里挂着旧版zlib.dll,运行阶段可能会因为符号冲突而出诡异崩溃。
3. 把lib和dll接进Qt工程:配置步骤与最小验证
3.1 先按标准目录结构整理文件
拿到编译好的包后,我习惯先把文件规整到项目的third_party目录下,结构清晰了后面配置才不容易出错:
third_party/ include/quazip/JlCompress.h include/quazip/quazip.h include/quazip/quazipfile.h lib/quazip.lib lib/quazipd.lib bin/quazip.dll bin/quazipd.dll bin/zlib.dll文件名里的d后缀表示Debug版本。QuaZip官方发布时通常同时带两个版本的导入库和动态库,后面配置时也要严格区分。目录不复杂,但这一步值得做扎实。
3.2 qmake工程里配置QuaZip链接
在.pro文件里,我通常这样写:
INCLUDEPATH += $$PWD/third_party/include CONFIG(debug, debug|release) { LIBS += -L$$PWD/third_party/lib -lquazipd } else { LIBS += -L$$PWD/third_party/lib -lquazip }说明一下这段逻辑。CONFIG(debug, debug|release)是qmake的常见写法,表示在Debug构建时链接带d的导入库quazipd,在Release构建时链接正常的quazip库。如果项目里Debug和Release都统一链同一个lib,有时能过编译,但运行时容易因为调试堆和发布堆不一致而崩溃。-L指定库搜索路径,-l指定库名,注意Qt的qmake不会自动帮你在路径后面补lib后缀。
3.3 CMake工程里配置QuaZip链接
用CMake的话,配置方式略有不同,重点是用IMPORTED库把路径和Debug/Release属性一次性说清楚:
set(QUAZIP_DIR "${CMAKE_CURRENT_SOURCE_DIR}/third_party") add_library(quazip SHARED IMPORTED) set_target_properties(quazip PROPERTIES IMPORTED_IMPLIB_RELEASE "${QUAZIP_DIR}/lib/quazip.lib" IMPORTED_IMPLIB_DEBUG "${QUAZIP_DIR}/lib/quazipd.lib" IMPORTED_LOCATION_RELEASE "${QUAZIP_DIR}/bin/quazip.dll" IMPORTED_LOCATION_DEBUG "${QUAZIP_DIR}/bin/quazipd.dll" ) target_include_directories(myapp PRIVATE "${QUAZIP_DIR}/include") target_link_libraries(myapp PRIVATE quazip)这里的IMPORTED_IMPLIB对应Windows上的.lib导入库,IMPORTED_LOCATION对应.dll运行文件。分开指明Debug和Release两份,CMake在生成工程时会根据当前的构建类型自动选择。值得留意的是,如果只是设置了IMPORTED_IMPLIB而漏掉IMPORTED_LOCATION,编译可能通过,但运行时会在加载dll这一步失败。
3.4 运行时dll的部署与最小验证
配置完构建,别急着写业务代码。先把dll部署到运行目录,否则程序启动就会提示找不到模块。在Windows上,最简单的方式是在项目里加一个构建后步骤,把bin目录下的dll拷贝到exe输出目录:
win32 { CONFIG(debug, debug|release) { QMAKE_POST_LINK += $$QMAKE_COPY $$shell_path($$PWD/third_party/bin/quazipd.dll) $$shell_path($$OUT_PWD/debug) $$escape_expand(\\n\\t) QMAKE_POST_LINK += $$QMAKE_COPY $$shell_path($$PWD/third_party/bin/zlib.dll) $$shell_path($$OUT_PWD/debug) $$escape_expand(\\n\\t) } else { QMAKE_POST_LINK += $$QMAKE_COPY $$shell_path($$PWD/third_party/bin/quazip.dll) $$shell_path($$OUT_PWD/release) $$escape_expand(\\n\\t) QMAKE_POST_LINK += $$QMAKE_COPY $$shell_path($$PWD/third_party/bin/zlib.dll) $$shell_path($$OUT_PWD/release) $$escape_expand(\\n\\t) } }这个写法看着啰嗦,但能把构建和部署一步做完。实际上,exe运行目录里至少要有quazip.dll和zlib.dll两个文件,缺任何一个都会直接启动失败。
部署完成后,我建议先跑这个最小验证程序:
#include <QCoreApplication> #include <QDebug> #include <JlCompress.h> int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); bool ok = JlCompress::compressFile("test.zip", "test.txt"); qDebug() << "compress result:" << ok; if (ok) { QStringList extracted = JlCompress::extractDir("test.zip", "out"); qDebug() << "extracted files:" << extracted; } return 0; }注意JlCompress::extractDir返回的是成功解压出来的文件路径列表。如果返回空列表,多半是ZIP文件没找对或者目标目录不可写。这个最小程序能把库文件、路径、依赖三个环节一次性验证完。
4. 核心API实战:从压缩到解压的五个常用场景
4.1 JlCompress一行完成目录压缩与解压
JlCompress是QuaZip所有静态工具方法里我使用频率最高的。压缩单个文件用compressFile,压缩目录用compressDir,解压目录用extractDir,这几个方法能覆盖日常大部分场景:
#include <JlCompress.h> // 压缩整个目录,包含子目录 QString zipFile = JlCompress::compressDir("work_backup.zip", "C:/project/data"); // 解压到本地目录 QStringList files = JlCompress::extractDir("work_backup.zip", "C:/output"); // 列出ZIP包里的所有文件 QStringList names = JlCompress::getFileList("work_backup.zip");注意compressDir的返回值是QString,成功时返回ZIP文件路径,失败时返回空字符串。用if (zipFile.isEmpty())判断是否成功比检查文件是否存在更可靠。compressDir默认递归压缩子目录,如果只想压一层,第二个参数传false就行。这个方法在大目录场景下很顺手。
4.2 用QuaZipFile逐文件写入,做细粒度控制
如果需要在压缩过程中改变目录结构、过滤文件名、写入注释,那就得绕过JlCompress,直接操作QuaZip和QuaZipFile。下面这段代码把不同来源的数据写入同一个ZIP包:
#include <QuaZip.h> #include <QuaZipFile.h> #include <QFile> bool createCustomZip(const QString &zipPath) { QuaZip zip(zipPath); if (!zip.open(QuaZip::mdCreate)) { return false; } QFile inFile("source.bin"); if (!inFile.open(QIODevice::ReadOnly)) { return false; } QuaZipFile outFile(&zip); if (!outFile.open(QIODevice::WriteOnly, QuaZipFile::ZIP_NO_ENCRYPTION)) { return false; } // 把文件数据整体写入ZIP条目,条目名为 custom/2024/data.bin outFile.write(inFile.readAll()); outFile.close(); inFile.close(); zip.close(); return true; }这段代码的关键点是QuaZipFile在打开之前,其名称和路径由当前ZIP内部的文件指针位置决定。如果想写入不同路径,需要在打开QuaZipFile之前调用zip.setCurrentFile("路径")。我一般在连续写入多个文件时用这个方式控制ZIP内部目录结构,例如把数据按日期分文件夹归档。
4.3 设置密码:AES加密与传统PKWARE加密
QuaZip支持给ZIP文件设置密码。传统加密方式向后兼容性最好。AES加密安全性更高,但也要求解压端支持AES算法,比如用较新版本的7-Zip或带解密插件的工具。
#include <QuaZip.h> #include <QuaZipFile.h> #include <QBuffer> bool createEncryptedZip(const QString &zipPath, const QByteArray &content) { QuaZip zip(zipPath); zip.setPassword("my-secret-pwd"); zip.setEncryptionMethod(QuaZip::EncryptionMethod::AES); if (!zip.open(QuaZip::mdCreate)) { return false; } QuaZipFile outFile(&zip); if (!outFile.open(QIODevice::WriteOnly, QuaZipFile::ZIP_AES)) { return false; } outFile.write(content); outFile.close(); zip.close(); return true; }这里有两个细节必须记住:一是密码必须在打开ZIP前设置,写在zip.open前面;二是在打开QuaZipFile时,加密参数要显式传QuaZipFile::ZIP_AES,否则写入出来的文件仍然是未加密的。另外,AES加密的ZIP文件在Windows资源管理器里双击打开会提示密码,但早期版本的解压工具可能直接报不可识别,这是算法兼容性问题,不是库的Bug。
4.4 读取ZIP内文件元信息
有些场景不需要解压,只需要读取ZIP包内有哪些文件、每个文件多大、最后修改时间是什么。用getFileList只能拿文件名,拿不到元信息:
#include <QuaZip.h> #include <QuaZipFileInfo.h> void dumpZipInfo(const QString &zipPath) { QuaZip zip(zipPath); if (!zip.open(QuaZip::mdUnzip)) { qWarning() << "无法打开ZIP包"; return; } for (bool more = zip.goToFirstFile(); more; more = zip.goToNextFile()) { QuaZipFileInfo info; if (!zip.getCurrentFileInfo(&info)) { continue; } qInfo() << info.name << "uncompressed:" << info.uncompressedSize << "compressed:" << info.compressedSize << "modified:" << info.dateTime.toString(Qt::ISODate); } zip.close(); }这里的循环写法值得记一下:goToFirstFile返回bool表示是否有第一个文件,goToNextFile返回bool表示是否还有下一个文件,用for循环一次遍历整个ZIP包。你可能会踩的坑是,遍历过程中一定要先把当前文件关闭,再跳到下一个,否则下一次goToNextFile会失败。
5. QuaZip集成避坑:五个我真实踩过的坑
5.1 程序启动即崩溃,提示找不到quazip.dll
现象:编译和链接都通过了,exe也生成了,双击运行直接弹出找不到quazip.dll的错误框,程序无法启动。原因:exe文件运行时按照系统搜索顺序寻找dll,常规搜索路径包括exe所在目录、系统目录和PATH变量。我只把dll放在了源目录,没有放到exe输出目录,运行时自然找不到。
解决办法:把quazip.dll和zlib.dll拷贝到exe同目录,或者用风帆系统环境变量设置PATH。我在实际项目中直接采用构建后自动拷贝的方案,也就是第3.4节写的QMAKE_POST_LINK,从根上避免这个问题。
5.2 Debug和Release库混用,编译能过运行崩
现象:Debug模式下链接了quazip.lib(不带d),编译正常,但运行到压缩操作时偶尔崩溃,而且崩溃位置不固定,表现得像内存被写坏。原因:Debug配置下Qt本身带调试符号和调试版堆检查,链接了Release版QuaZip库后,库内部使用的堆操作方式和Qt调试堆不一致,跨模块传递对象时就会出问题。解决办法:严格按构建类型区分导入库,Debug用quazipd库,Release用quazip库。从那以后我每次新建工程第一件事就是把两份库的路径在pro里写死,不再手动改。
5.3 中文文件名解压后乱码
现象:代码里写入ZIP的中文文件名没有乱,但用Windows资源管理器解压后文件名变成乱码。原因:QuaZip在写入文件名时按ZIP规范默认使用UTF-8编码,但早期Windows解压器会尝试用本地代码页解析文件名。如果文件名在ZIP内标了UTF-8,Windows新版大多能正确处理,但某些第三方解压工具会忽略标记,按本地GBK去解码。解决办法:写入前让文件名保持UTF-8,不要手动转GBK。如果外部解压工具仍然乱码,可以用QuaZipFileInfo拿到原始UTF-8名称后自行转换,或者在使用时明确要求解压时带上Unicode标志位。
5.4 Qt5编译的库拿到Qt6工程里链接,报一大串符号错误
现象:链接时出现几百个类似unresolved external symbol "public: __cdecl QuaZip::...的报错。原因:Qt6对QString等基础类型做了ABI层面的调整,QuaZip的头文件和库如果还停留在Qt5,在Qt6编译环境下会解析出不一致的符号签名。解决办法:拿到编译好的库文件时,先确认版本。不同Qt主版本对应的库二进制不通用,Qt6工程必须使用基于Qt6编译的QuaZip库。如果手里只有Qt5版本,要么升级编译器时一并重编QuaZip,要么降级Qt版本。
5.5 项目依赖多个ZIP库导致zlib符号冲突
现象:软件还同时使用另一个直接依赖zlib的动态库,启动后QuaZip的压缩操作时好时坏,内存报错。原因:zlib在同一进程内被加载两份,Windows下同名dll不会自动去重,调用时可能落到另一份zlib上,造成符号混乱。解决办法:保证进程内只有一份zlib.dll,优先把QuaZip需要的zlib放到exe目录优先加载,再检查其他依赖库是否带自带版本,同名文件尽量用同一个。如果冲突严重,考虑换用静态链接的QuaZip,让zlib代码编入主程序,从根上避免两份dll的问题。
6. 进阶技巧:给ZIP包追加文件、流式读取与内存控制
6.1 给已有ZIP追加文件而不是重新压缩整个目录
前期备份已经生成了archive.zip,新产生了一个文件要加进去,重新压缩整个目录效率很低。QuaZip有追加模式,可以原地打开已有ZIP包,写入新条目:
bool appendFileToZip(const QString &zipPath, const QString &filePath) { QuaZip zip(zipPath); if (!zip.open(QuaZip::mdAppend)) { return false; } QFile file(filePath); if (!file.open(QIODevice::ReadOnly)) { return false; } QuaZipFile outFile(&zip); if (!outFile.open(QIODevice::WriteOnly, QuaZipFile::ZIP_NO_ENCRYPTION)) { return false; } outFile.write(file.readAll()); outFile.close(); file.close(); zip.close(); return true; }要留意的是,QuaZip的追加模式是在ZIP文件末尾追加新条目,已有文件不会被重写。如果追加的文件名和之前重复,ZIP包会出现两个同名条目,解压时不同工具的表现不一致,有些取第一个,有些取最后一个。所以追加前最好先查一下现有文件列表,避免重复写入。
6.2 读取ZIP内文件而不落地到磁盘
有一种常见需求是直接从ZIP包里读取某个文件的内容做解析,不生成临时文件。QuaZipFile支持读模式,直接流式读出:
QByteArray readEntryDirectly(const QString &zipPath, const QString &entryName) { QuaZip zip(zipPath); if (!zip.open(QuaZip::mdUnzip)) { return {}; } zip.setCurrentFile(entryName); QuaZipFile file(&zip); if (!file.open(QIODevice::ReadOnly)) { return {}; } QByteArray data = file.readAll(); file.close(); zip.close(); return data; }这个函数适合读配置文件、读取小体积文本、校验某个条目是否完整。注意setCurrentFile传入的条目路径必须和ZIP内存储的路径完全一致,包括大小写和斜杠方向。ZIP内部统一用正斜杠,Windows下如果你把路径写成了反斜杠,查找就会失败。
6.3 大文件压缩时的内存控制
QuaZipFile继承自QIODevice,可以按块读写。对超过几百MB的文件,我一般用循环分块而不是一次性readAll:
bool compressLargeFile(QuaZipFile &outFile, const QString &path) { QFile f(path); if (!f.open(QIODevice::ReadOnly)) { return false; } char buf[1024 * 256]; qint64 readBytes = 0; while ((readBytes = f.read(buf, sizeof(buf))) > 0) { if (outFile.write(buf, readBytes) != readBytes) { return false; } } f.close(); return true; }这个写法让内存占用保持在一个固定缓冲区大小,不随文件体积增长。真正处理大文件压缩时,相比一次性readAll的表现其实要稳得多。我遇到过一次把2GB文件直接readAll后内存飙高导致程序被系统杀掉的状况,改成循环分块后一切正常。从那以后我每次涉及压缩逻辑,都会习惯性地确认输入输出两侧是否按块流动,这个习惯帮我避掉了不少性能与稳定性的坑。希望这篇QuaZip集成笔记也能帮你少走几步弯路。
本文还有配套的精品资源,点击获取