简介:面向Windows C++开发者的QuaZip预编译库压缩包,解决了在Qt/C++项目中直接处理ZIP/RAR档案时的集成痛点。QuaZip作为开源压缩库,支持ZIP档案的创建、读取、遍历、添加和删除,并对RAR提供基本读取能力;包内已编译好静态库(.lib)与动态库(.dll),同时附带头文件、CMake配置及pkg-config说明,无需从源码自行编译,可快速接入Visual Studio或Qt5工程。压缩包共29个文件,以头文件、CMake脚本和库文件为主体,整体仅427KB,目录结构清楚,方便按需引用。已有595人学习下载。借助这套编译产物,开发者能直接调用QuaZip API完成ZIP档案的打开、读写、条目枚举与增删操作,适合需要为C++桌面应用快速加装压缩功能的中高级程序开发者。 实际做Qt开发的朋友,应该都遇到过这种需求:客户端要压缩日志、批量打包文件、或者倒腾一个zip归档格式,有时候还得解压带密码的压缩包。网上搜一圈,有一半人会告诉你直接用QProcess调外部exe,另一半人会把QuaZip的GitHub地址甩给你。前者的坑在于你得依赖系统环境,用户的机器上没装解压工具就全完蛋;后者的坑在于,QuaZip是个C++库,官方并不直接发布编译好的lib和dll,你得自己动手把源码编成库,才能在你的Qt工程里#include <quazip/quazip.h>。
我在几个项目里都踩过这趟浑水,期间也折腾过编译期各种奇奇怪怪的报错,比如unreferenced label、dll load failed、运行时忽然冒出来的0xc000007b,甚至因为机器上装了好几个版本的zlib导致dll冲突。这次我把整个QuaZip编译过程,从原理到实操,再到常见的dll坑,完整整理出来,希望对正在为Qt压缩功能挠头的朋友有点帮助。
1. 动手编译前,先把这三件事想明白
1.1 为什么要自己编译而不是直接下载现成的库
很多人在Qt里想用第三方库,第一反应是去官方下载“编译好的库”。好消息是zlib、openssl这些老牌库基本都有官方的Windows预编译包;坏消息是QuaZip这种基于Qt生态的封装库,官方仓库的Release页面只会放源码包,没有任何一个.dll或.lib给你下载。原因很简单:Qt的版本实在太多了,5.15、6.2、6.5、6.7,每一个大版本之间的ABI都不一样,MinGW编译器和MSVC编译出的库也不能混用,再叠加x86/x64架构的差异,任何一份“预编译二进制”都只能在特定的小圈子里生效。
手动编译的核心意义,就是用自己的Qt版本和编译器,生成一组“完全匹配”的库文件。这里说的匹配,不是版本号长得像就行,而是要精确到:
- Qt版本一致:比如你的项目用的是
Qt 6.5.3 MSVC2019 64bit,QuaZip就必须在这个环境下编译; - 编译器一致:MSVC的库不能给MinGW用,MinGW的库不能给MSVC用,这是C++ ABI决定的;
- 构建类型一致:Debug工程链Debug库,Release工程链Release库,混用会在运行时炸给你看;
- 位数一致:x64的库不能喂给x86的exe,反之亦然。
这其实就是“编译原理”最朴素的一面:源码本身是“中性的”,但编译产物是带着编译器基因的。你要是拿一个MinGW编译的QuaZip库去链接MSVC的Qt工程,链接器不报LNK2019才怪。
1.2 编译产物里为什么既有lib又有dll
如果你平时用Linux比较多,会对.so和.a比较熟;Windows上这套东西恰好是反过来拆得更细:动态库模式会同时产出.dll和.lib。
这里要重点说清楚,QuaZip编译出来那个.lib(如果有的话),不是静态库,而是“导入库”(Import Library)。它体积很小,几十KB到几百KB不等,里面不包含实际代码,只记录了dll导出了哪些函数符号、符号在dll的什么位置。你可以把它理解成书架上的索引卡片,真正的书(代码)在dll里。编译链接的时候,链接器只需要看索引卡片,知道“哦,QuaZip::open()这个函数在dll里存在,行,链接通过”;等程序跑起来,Windows加载器才会根据索引卡片找到dll,把真正的函数地址填上。
提示:判断一个lib是静态库还是导入库,直接看文件大小就八九不离十。QuaZip的静态lib一般有几MB,而导入lib只有几KB。别把两者搞混了,否则可能出现“链接过了,一运行就报找不到dll”的现象。
1.3 编译器、架构、Qt版本三件套必须匹配
我在新手阶段踩过最大的坑,就是以为“反正都是C++库,不同编译器编出来的库应该差不多吧”。结果在MSVC环境下链一个MinGW编出来的QuaZip,链接器报了一堆unresolved external symbol错误。后来我才明白,MSVC和MinGW背后用的C++运行时库、符号修饰规则完全不同,两者编译出来的对象文件在ABI层面根本不互通。
| 环境要素 | 兼容性要求 | 不匹配时的典型表现 |
|---|---|---|
| Qt 版本 | 使用同一套 Qt(如都为 6.5.3) | 头文件版本和库版本不一致,编译期报错或运行期莫名崩溃 |
| 编译器 | MSVC 链 MSVC,MinGW 链 MinGW | LNK2019、LNK2001、undefined reference |
| Debug/Release | Debug 链 Debug,Release 链 Release | 运行期内存错误、DLL load failed |
| x86/x64 | 位数完全对齐 | 程序启动报0xc000007b应用无法正常启动 |
所以在你敲任何编译命令之前,先打开Qt Creator看一眼,确认你当前用的Kit是什么。这决定了后面所有参数怎么写。
2. 完整的编译流程:从源码到lib和dll
2.1 准备工具和源码
先说需要的材料清单:
- QuaZip源码:去GitHub上搜
stachenov/quazip,把当前release版源码下载下来。我用的版本比较新,对Qt5和Qt6都支持。 - zlib依赖:QuaZip是建立在zlib之上的封装,所以必须先有zlib的库。这里有个省事技巧:如果你的Qt是官方在线安装器安装的,Qt源码包里其实自带了一份zlib源码,位置一般在
Qt安装目录/Src/qtbase/src/3rdparty/zlib/src,可以直接用这份源码编zlib库。如果不想这么麻烦,也可以单独下zlib的官方源码编一份,流程差不多。 - CMake:QuaZip官方支持CMake构建,这也是主力构建方式。Qt安装目录里通常带着CMake,命令是
Qt/Tools/CMake/bin/cmake.exe。 - 编译工具链:MSVC环境用VS自带的开发者命令行,或者直接在Qt Creator里打开带Kit的终端;MinGW环境用Qt安装目录下的
mingw73_64、mingw1120_64这类目录里的g++。
目录结构上,我习惯单独建立ThirdParty管理第三方库,大概长这样:
ThirdParty/ src/ quazip-1.4/ # QuaZip源码 zlib-1.3/ # zlib源码(可选) build/ quazip-build/ zlib-build/ installed/ quazip/ zlib/源码和构建目录分开,是因为CMake在源码目录里生成一堆中间文件会很脏,以后想升级版本也不方便。
2.2 用CMake配置和编译
这里我以MSVC + Qt 6.5.3 x64为例,完整走一遍命令。先说最核心的CMake配置命令:
cmake -S <path-to-quazip-src> -B <path-to-build> \ -DCMAKE_PREFIX_PATH=C:/Qt/6.5.3/msvc2019_64 \ -DCMAKE_INSTALL_PREFIX=C:/ThirdParty/installed/quazip \ -DCMAKE_BUILD_TYPE=Release \ -DQUAZIP_QT_MAJOR_VERSION=6 \ -DQUAZIP_USE_QT_ZLIB=ON这几个参数的含义,我挨个解释一下:
CMAKE_PREFIX_PATH:告诉CMake你的Qt在哪。CMake会去这个目录下找Qt6Config.cmake之类的配置文件,从而推断出Qt的编译选项、库路径和生成的头文件路径;CMAKE_INSTALL_PREFIX:编译完成后“安装”的目录,头文件、lib、dll、cmake配置文件都会统一放到这里。相当于给你的QuaZip做一个“干净的交付目录”;CMAKE_BUILD_TYPE:Release还是Debug。这条在MSVC的CMake生成阶段影响没有Makefile生成的大,但最好是显式指定;QUAZIP_QT_MAJOR_VERSION=6:明确指定Qt主版本号为6,避免CMake自动检测出岔子;QUAZIP_USE_QT_ZLIB=ON:使用Qt内部集成的zlib。注意,这个选项不总是有效,因为Qt6的zlib是私有模块,不公开导出符号。如果后续链接时报错找不到zlib符号,就得回到“自己编一份zlib”的方案,然后加一个-DZLIB_ROOT参数指过去。
配置命令执行完,如果你看到Configuring done和Generating done,说明CMake这一步过了。接下来编译:
cmake --build <path-to-build> --config Release --parallel 8 cmake --install <path-to-build>--parallel 8是并行编译,能明显加快速度。cmake --install就是把编译产物复制到你设置的CMAKE_INSTALL_PREFIX目录里。
编译完成后,去C:/ThirdParty/installed/quazip下面看一眼,正常情况下应该是这样的结构:
include/ quazip/ quazip.h quazipfile.h ... lib/ quazip.lib quazip.dll (或者 quazipd.lib / quazipd.dll, debug版本)这里有个细节:在MSVC和CMake的默认配置下,Release产出quazip.dll和quazip.lib,Debug产出quazipd.dll和quazipd.lib。名字后面带不带d,代表Debug和Release的区别。你如果在网上看到有人编译出来叫quazip1.dll之类带版本号的文件名,那是他在CMake里额外设置了OUTPUT_NAME或版本后缀,不影响使用。
再说MinGW的情况。MinGW环境下用CMake命令差不多,但需要注意编译器路径和生成的make工具是MinGW Makefiles或Ninja:
cmake -S <path-to-quazip-src> -B <path-to-build> \ -G "MinGW Makefiles" \ -DCMAKE_PREFIX_PATH=C:/Qt/6.5.3/mingw_64后面同样执行cmake --build和cmake --install。MinGW编出来的dll对MSVC工程完全不兼容,反之亦然,这一点再怎么强调也不为过。
2.3 如何确认产物是对的
很多时候你已经编译出dll和lib了,但是项目一跑还是报错。这时候不要瞎试,先查一下这个dll依赖了哪些东西。
在Windows下有两个工具非常实用:
- Visual Studio自带的
dumpbin:打开“Developer Command Prompt for VS”,执行dumpbin /dependents quazip.dll,会列出这个dll依赖的所有其他dll,比如zlibwapi.dll、Qt6Core.dll等。如果你没看到任何Qt相关的依赖,那很可能是编了一个空壳; - 第三方工具
Dependencies(原名Dependency Walker的增强版):图形化界面,拖进去就能看到dll依赖树。如果你项目启动时提示找不到Qt6Core.dll,排查效率比纯命令行高很多。
确认依赖之后,再看位数。在VS开发者命令行里执行dumpbin /headers quazip.dll,输出信息里有一行machine (x64),如果显示x86,说明这是32位版本,而你如果用64位exe去加载它,必然会报0xc000007b错误。
3. 在自己的Qt项目里集成lib和dll
3.1 qmake(.pro文件)集成方式
假设你的项目还在用qmake构建,集成步骤很简单。把编译好的头文件和库放到项目能访问到的地方后,在.pro文件里加入:
INCLUDEPATH += C:/ThirdParty/installed/quazip/include LIBS += -LC:/ThirdParty/installed/quazip/lib -lquazip如果你在Debug模式下链接的是quazipd,Release模式链接的是quazip,建议在.pro里做区分:
CONFIG(debug, debug|release) { LIBS += -L$$PWD/../ThirdParty/quazip/lib -lquazipd } else { LIBS += -L$$PWD/../ThirdParty/quazip/lib -lquazip }这里有个特别容易踩的坑:LIBS里写的库名,在MSVC下不要带.lib后缀也不要带d后缀,在MinGW下则是-lquazip和-lquazipd的差异。如果你写-lquazip.lib,MSVC的链接器未必报错,但MinGW的链接器大概率直接说不认识这个目标文件。
链接完成后,要把quazip.dll(以及zlib相关的dll)拷贝到exe同级的目录下。Qt自带的windeployqt工具不会帮你自动拷贝QuaZip的dll,因为它只能扫描出Qt自身的依赖,识别不了QuaZip这种第三方库。我习惯在.pro里加一个自定义步骤:
QMAKE_POST_LINK += $$quote(cmd /c copy /Y C:/ThirdParty/installed/quazip/lib/quazip.dll $$OUT_PWD/$$DESTDIR/)这样每次编译完自动把dll拷过去,省得手动复制忘掉。
3.2 CMake工程集成方式
如果你的项目是用CMake组织的,事情更简单。QuaZip安装后会在CMAKE_INSTALL_PREFIX下生成cmake配置文件目录,我们直接在工程的CMakeLists.txt里:
find_package(QuaZip QUIET) if(QuaZip_FOUND) target_link_libraries(your_target PRIVATE QuaZip::QuaZip) else() # 找不到就手动指定路径 include_directories(C:/ThirdParty/installed/quazip/include) target_link_libraries(your_target PRIVATE C:/ThirdParty/installed/quazip/lib/quazip.lib) endif()find_package的方式更优雅,它会自动带好头文件路径和dll搜索路径。不过使用find_package有一个前提条件:你安装QuaZip时的CMAKE_INSTALL_PREFIX路径和当前工程里find_package的搜索路径必须对得上。如果对不上,可以在调用find_package前加一行:
set(QuaZip_DIR "C:/ThirdParty/installed/quazip/lib/cmake/QuaZip")指向带QuaZipConfig.cmake的那个具体目录。
3.3 运行时和发布时的dll管理
编译链接只是第一步,发布的时候如果没管好dll,用户那边一启动就会弹出“找不到quazip.dll”或“找不到zlib.dll”的错误框。
这里分享一个我屡试不爽的发布策略:先让程序在本地跑通,再用Dependencies工具确认exe依赖的所有第三方dll,最后把所有非系统dll统一扔到exe同级目录。不要想着把dll放到C:/Windows/System32里,不同的程序可能依赖不同版本的zlib,放系统目录反而容易引发dll冲突。
在实际项目里我还遇到过一种情况:机器上装了两个Qt程序,一个带的QuaZip是1.3版,一个是1.4版,两个dll都通过系统PATH或某个公共目录被加载,结果后启动的程序加载错了版本,运行时就开始乱报内存错误。这种dll冲突很隐蔽,查起来也费劲。最彻底的解决办法是把QuaZip静态编译进你的程序里,这样发布时就只有一个exe,不会拖着一堆dll去跟别的程序打架。
注意:QuaZip是基于LGPL协议开源的,如果你选择静态编译并闭源分发,需要仔细评估LGPL的合规要求,这点在做商业化产品时一定要想清楚。
4. 踩坑合集:dll加载失败、冲突与修复思路
4.1 最常见的运行时错误对照表
| 错误现象 | 可能原因 | 排查思路 |
|---|---|---|
| 启动报“找不到quazip.dll” | dll没拷贝到exe同级目录,或路径不在PATH环境变量中 | 检查exe目录,用Dependencies看exe依赖列表 |
启动报0xc000007b | 架构不匹配,32位dll被64位exe加载,或反之 | dumpbin /headers查看dll的machine类型 |
链接时报LNK2019、无法解析的外部符号 | 编译器不匹配/架构不匹配/头文件与库不是同一份代码生成 | 确认Kit的编译器和架构,确认头文件include路径 |
| 运行中莫名崩溃,无明确报错 | Debug/Release混用,或两份不同版本dll被同时加载 | 用Dependencies检查运行时实际加载的dll路径 |
| CMake配置阶段找不到zlib | QUAZIP_USE_QT_ZLIB=ON无效,或找不到zlib头文件 | 手动编译zlib,加-DZLIB_ROOT参数指定路径 |
4.2 关于“dll修复工具”的正确认知
网上搜“dll修复工具”,能跳出一堆号称一键修复的软件。我强烈建议开发者和普通用户都不要随便装这种东西。这些工具的原理多半是扫描系统目录,然后从不知名来源下载一个同名dll塞进去。且不说下载的dll是不是原版、带不带后门,单说“把某个dll覆盖到系统目录”这个操作,就极有可能破坏其他正常运行的程序,反而制造出新的dll冲突。
真正正确的做法只有两种:
- 如果你缺的是自己项目里的第三方dll,就用上面讲的方式自己编译、自己拷贝,知根知底;
- 如果你缺的是系统的VC运行库(比如
msvcp140.dll),那就去微软官网下载对应版本的“Visual C++ Redistributable for Visual Studio”安装包,这是官方途径,不会引入安全问题。
4.3 多版本QuaZip共存时的处理方案
开发过程中最痛苦的,不是第一次编译QuaZip,而是后来项目里出现了“编译能用,运行时莫名踩内存”的问题。排查到最后,往往发现是同一个目录下存在多个版本的QuaZip dll,Windows加载器按照搜索路径优先找到了老版本,跟新编译的lib对不上,结果就是内存布局不一致,运行到某个析构函数时直接崩溃。
我的处理思路是这样的:
- 统一版本:全项目只保留一个QuaZip版本,源码统一管理,不用的旧版本dll从所有系统路径、编译器搜索路径中清除;
- 显式指定加载路径:在程序初始化时用
QLibrary::setLoadHints和绝对路径加载quazip,或者用GetModuleHandle确认实际加载的是哪个dll; - 如果项目里多个模块需要不同版本QuaZip,建议把其中一个模块改成静态链接,彻底回避dll地狱。
这几个步骤下来,我后来再也没被QuaZip的dll问题折磨过。
5. 一个小技巧:把QuaZip的cmake配置目录一起拷贝
最后分享一个我自己用得很舒服的小习惯。上面提到cmake --install之后,不只生成了dll和lib,还有一个cmake目录,里面存了QuaZipConfig.cmake等一系列CMake包配置文件。这个目录很多人会忽略,但实际上它对你的团队协作简直不要太友好。
把整个CMAKE_INSTALL_PREFIX目录(包括include、lib、cmake三个子目录)提交到你们团队内部的依赖管理仓库或者直接放进Git子模块里,新同事拉下代码后,在CMakeLists里写一行find_package(QuaZip REQUIRED)就能直接用了。再也不用费力解释“QuaZip去哪下载”“怎么编译”之类的问题。
这个思路同样适用于zlib、libzip、bit7z这类第三方库。提前把编译好的库做成一个标准的“交付目录”,配合CMake的配置文件,后续在Qt里接各种开源库都会顺手很多。
本文还有配套的精品资源,点击获取