资讯动态

MINIZIP压缩解压缩:zlib分工、静态库集成与内存流打包

发布时间:2026/10/24 4:33:04 来源:尧图企业网站定制
简介面向需要在C/C项目中集成ZIP压缩解压能力的开发者这份MINIZIP工程包提供了可直接链接使用的静态库与配套头文件。MINIZIP基于ZLIB构建接口简洁、体积轻量适合嵌入式与资源受限环境并支持跨平台调用和UTF-8等多字节文件名处理经改动后还能输出整体与单个文件的解压进度。压缩包共7个文件约137KB包含5个h头文件如mz.h、mz_zip.h、mz_strm.h等分别对应核心定义、ZIP读写与流式IO接口和2个lib静态库zlibstatic.lib与minizip.lib免去自行编译ZLIB与MINIZIP的麻烦解压后即可加入工程构建设置。目前已有877人学习下载。读者可借此跳过底层压缩算法与编译配置环节直接在工程中调用zip/unzip系列接口完成打包与解压并借助进度回调改善大文件处理的交互体验同时也可参考其中的错误处理与文件IO配合方式减少集成过程中的常见问题。1. 一个导出诊断包的需求MINIZIP 压缩解压缩到底省在哪去年给一个桌面端工具加「一键导出诊断包」十几个日志文件、一份配置快照、一个 dump 头要打成一个 zip 发给对接方。试过调系统的压缩接口依赖资源管理器那一套在无人值守场景里跑不起来引重型压缩库又要多背十几 MB 的运行库。最后落在 MINIZIP 压缩解压缩这套组合上——zlib 管 deflate 压缩流minizip 管把多个文件拼成标准 zip 结构。资源里附了编译好的 zlibstatic.lib 和 minizip.lib省掉自己拉源码配 CMake 编库那一步。它适合 C/C 桌面程序、上位机、内部工具这类「不想引重依赖、又要产物能被任何解压软件打开」的场合不适合需要 AES 加密、需要分卷、需要流式随机写的重活。2. zlib 与 minizip 的分工谁在压谁在打包拿到两个 .lib 的人最容易犯的错是以为随便调一个函数就能产出 zip。实际上这两个库的职责切得非常干净混淆了就会写出「能压能解、但别的软件打不开」的畸形文件。2.1 zlib 只认字节流不认文件目录zlib 的输入输出都是连续内存块。你给它 100KB它吐给你 30KB deflate 数据中间它不关心里面是日志还是图片也不记录任何元信息。最小调用链是deflateInit2→deflate循环 →deflateEnd解压侧对称地用inflateInit2→inflate→inflateEnd。// 把一段内存压缩成裸 deflate 流 z_stream zs; memset(zs, 0, sizeof(zs)); // 关键参数是第 4 个 windowBits // 正数如 15 - 输出带 2 字节 zlib 头和 4 字节 adler32 校验 // 负数如 -15 - 输出裸 deflate 流zip 内部要的就是这个 // 很多人第一次写错就错在这里压出来的数据塞进 zip 后解压软件直接报数据错误 int rc deflateInit2(zs, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); if (rc ! Z_OK) return false; zs.next_in (Bytef *)src; zs.avail_in (uInt)src_len; zs.next_out (Bytef *)dst; zs.avail_out (uInt)dst_cap; rc deflate(zs, Z_FINISH); // Z_FINISH 表示这是最后一块 size_t written dst_cap - zs.avail_out; deflateEnd(zs);memLevel取 8 是默认值调大更吃内存换速度调小反过来strategy用Z_DEFAULT_STRATEGY就够纯文本日志可以试Z_FILTERED压缩率通常能再高一两个点。这些参数都是锦上添花真正决定「能不能用」的是 windowBits 的正负号。zlib 还提供crc32()这个函数在 zip 场景里必须用上——zip 每个条目头里都要存原始数据的 CRC32解压端拿它校验完整性。zlib 只给你算 CRC 的工具不负责把它写到文件里。2.2 minizip 补上的三件事目录、中央索引、CRCzip 格式的本质是一个「数据区 尾部索引」的容器结构大致如下区域内容由谁写出Local File Header签名 0x04034b50、版本、压缩方法、CRC、压缩/原始大小、文件名minizipFile Datadeflate 压缩后的字节流zlibData Descriptor流式写出时事后回填的 CRC 和大小可选minizipCentral Directory每个文件一条记录签名 0x02014b50含本地头偏移minizipEnd of Central Directory签名 0x06054b50记录条目总数、中央目录偏移和长度minizip看懂这张表就明白为什么光有 zlibstatic.lib 做不出 zipzlib 只覆盖了第二行。你当然可以自己手写本地头和中央目录但这活儿琐碎且极易出错——偏移算错一个字节整个包就废了。minizip 把这层封装成几个函数调用顺序和结构一一对应zipOpen打开/创建目标文件zipOpenNewFileInZip写本地文件头zipWriteInFileInZip内部调 deflate 写压缩数据zipCloseFileInZip结束当前条目回填 CRC 和大小zipClose写中央目录和 EOCD这套调用是严格顺序的中间任何一步被跳过产出的都是半成品。这也是后面避坑章节里 CRC 报错的主要来源。2.3 静态库选型/MT 和 /MD 对不上会怎样资源给的是「编译好的」.lib这句话背后藏着一个前提它是在某个特定的运行库配置下编出来的。MSVC 的 C 运行库有四套组合库和工程必须落在同一格配置宏运行库Debug 静态_DEBUG_MTLIBCMTDRelease 静态_NDEBUG_MTLIBCMTDebug 动态_DEBUG_MDMSVCRTDRelease 动态_NDEBUG_MDMSVCRT拿库之前先验身别信文件名# 查看 .lib 里记录的默认运行库一眼看出它是 /MT 还是 /MD 编的 dumpbin /directives zlibstatic.lib | findstr /i DEFAULTLIB dumpbin /directives minizip.lib | findstr /i DEFAULTLIB输出里出现LIBCMT就是静态运行库出现MSVCRT就是动态运行库带D后缀的是 Debug 版。混搭的后果分两种链接期报LNK2038 检测到 RuntimeLibrary 的不匹配项或者更阴的——链接过了运行到free()时崩因为一块内存在一个堆上分配、在另一个堆上释放。后者查起来能耗掉一整天血泪经验就是别省这一步。提示如果手上只有一种配置的库而工程四种配置都要出包最省事的做法是改工程配置去迁就库而不是想办法转换 .lib。3. 把 .lib 接进 VS 工程三个配置项与第一份压缩代码库验完身接下来是把它接进工程。这一步没有技术含量但填错一个路径就是一串 LNK2019值得把每一步写清楚。3.1 包含目录、库目录、附加依赖项的实际填法先按配置把目录分好别把 Debug 和 Release 的库堆在一个文件夹里third_party/zlib-minizip/ ├── include/ zlib.h zconf.h zip.h unzip.h ioapi.h └── lib/ ├── x64/Debug/ zlibstatic.lib minizip.lib └── x64/Release/ zlibstatic.lib minizip.lib用 CMake 的话这样接比在属性页里点鼠标更容易复现cmake_minimum_required(VERSION 3.15) project(zipdemo CXX) set(MZ_ROOT ${CMAKE_SOURCE_DIR}/third_party/zlib-minizip) add_executable(zipdemo main.cpp) target_include_directories(zipdemo PRIVATE ${MZ_ROOT}/include) # 用生成器表达式按配置挑目录避免 Debug 工程链到 Release 库 target_link_directories(zipdemo PRIVATE $IF:$CONFIG:Debug,${MZ_ROOT}/lib/x64/Debug,${MZ_ROOT}/lib/x64/Release) # 顺序有讲究minizip 依赖 zlib被依赖的放后面 target_link_libraries(zipdemo PRIVATE minizip zlibstatic)手填属性页的话对应三个位置C/C → 常规 → 附加包含目录链接器 → 常规 → 附加库目录链接器 → 输入 → 附加依赖项填minizip.lib;zlibstatic.lib。附加依赖项的顺序在 MSVC 下不像 GCC 那么敏感但把被依赖的库放后面是通用习惯跨平台移植时不至于翻车。3.2 从零写一个 zipzipOpen 到 zipClose 的完整调用链下面这段能直接跑输入两个文件路径输出一个标准 zip// zip_pack.cpp : 把若干文件打进一个 zip #include cstdio #include cstring #include string #include vector extern C { #include zlib.h #include zip.h } // 整文件读入内存。诊断包这种几十 MB 的场景够用上 GB 请改成分块读写 static bool read_all(const std::string path, std::vectorunsigned char out) { FILE *fp fopen(path.c_str(), rb); if (!fp) return false; fseek(fp, 0, SEEK_END); long n ftell(fp); fseek(fp, 0, SEEK_SET); out.resize(n 0 ? (size_t)n : 0); bool ok (n 0) || (fread(out.data(), 1, (size_t)n, fp) (size_t)n); fclose(fp); return ok; } static bool add_entry(zipFile zf, const std::string arcname, const std::string srcpath) { std::vectorunsigned char buf; if (!read_all(srcpath, buf)) return false; // zip_fileinfo 里可以填 tmz_date 保留源文件修改时间 // 全零的话解压出来时间显示为 1980-01-01功能不受影响但看着别扭 zip_fileinfo zfi; memset(zfi, 0, sizeof(zfi)); // 参数依次为zip句柄、包内路径、文件信息、 // 本地extra、全局extra、注释、压缩方法、压缩级别 // 压缩级别取 0~9-1 等价于 Z_DEFAULT_COMPRESSION约等于 6 int err zipOpenNewFileInZip(zf, arcname.c_str(), zfi, nullptr, 0, nullptr, 0, nullptr, Z_DEFLATED, Z_DEFAULT_COMPRESSION); if (err ! ZIP_OK) return false; if (!buf.empty()) { // 注意第三个参数是 unsigned单次上限 4GB-1大条目必须循环分块写 err zipWriteInFileInZip(zf, buf.data(), (unsigned)buf.size()); if (err ! ZIP_OK) { zipCloseFileInZip(zf); return false; } } // 这一步负责回填 CRC 和压缩后大小漏掉它条目就是残的 return zipCloseFileInZip(zf) ZIP_OK; } int main() { zipFile zf zipOpen(diag_pack.zip, APPEND_STATUS_CREATE); if (!zf) return 1; bool ok add_entry(zf, logs/app.log, C:/diag/logs/app.log); ok add_entry(zf, cfg/settings.ini, C:/diag/cfg/settings.ini) ok; // 第二个参数是全局注释可以传 nullptr // zipClose 才会写中央目录不调用则整个包不可用 if (zipClose(zf, nullptr) ! ZIP_OK) ok false; return ok ? 0 : 2; }几个参数值得单独说。APPEND_STATUS_CREATE表示新建存在则覆盖另有APPEND_STATUS_CREATEAFTER用于在已有文件后追加APPEND_STATUS_ADDINZIP用于往已有 zip 里加条目——追加模式要求目标文件本身是合法 zip随手传一个空文件进去会失败。Z_DEFLATED是唯一常用的压缩方法取 0 表示Z_NO_COMPRESSION直接存储适合已经压过的图片、视频反而更快。包内路径用正斜杠分隔不要用反斜杠。Windows 的解压工具大多能容忍反斜杠但跨平台就未必了。3.3 解压侧unzGoToFirstFile 遍历与 unzReadCurrentFile 读块解压是「先定位条目再打开当前条目再循环读块」三步#include cstdio #include cstring #include string #include vector extern C { #include zlib.h #include unzip.h } static bool extract_all(const char *zip_path, const char *out_dir) { unzFile uf unzOpen64(zip_path); if (!uf) return false; // number_entry 是条目总数注意它是 uLong 类型 // 在 32 位下超过 4G 个条目会溢出正常包不会遇到 unz_global_info gi; if (unzGetGlobalInfo(uf, gi) ! UNZ_OK) { unzClose(uf); return false; } for (uLong i 0; i gi.number_entry; i) { char name[512] {0}; unz_file_info fi; if (unzGetCurrentFileInfo(uf, fi, name, sizeof(name), nullptr, 0, nullptr, 0) ! UNZ_OK) break; size_t len strlen(name); if (len 0 name[len - 1] /) { // 目录条目按需 mkdir这里只跳过 } else if (unzOpenCurrentFile(uf) UNZ_OK) { std::string out std::string(out_dir) / name; // 子目录要先建好 FILE *fp fopen(out.c_str(), wb); std::vectorunsigned char chunk(64 * 1024); int n; // 返回值 0 是读到的字节数0 表示读完0 是错误码 while (fp (n unzReadCurrentFile(uf, chunk.data(), (unsigned)chunk.size())) 0) { fwrite(chunk.data(), 1, (size_t)n, fp); } if (fp) fclose(fp); // 这里返回值必须查UNZ_CRCERROR(-105) 说明解出的数据和头里的 CRC 对不上 int rc unzCloseCurrentFile(uf); if (rc ! UNZ_OK) { unzClose(uf); return false; } } if (i 1 gi.number_entry) unzGoToNextFile(uf); } unzClose(uf); return true; }unzReadCurrentFile的缓冲区别开太小64KB 是个平衡点开 4KB 会让解压速度掉一截开 1MB 对小文件又浪费。unzOpen64和unzOpen的差别只在路径参数类型前者接受const void*后者是const char*64 位平台建议统一用带 64 的版本。4. 内存流改造把 zip 直接打在内存里不落盘很多场景不允许落临时文件诊断包要直接从内存 POST 出去或者打包结果要存进数据库的 blob 字段。minizip 对此留了口子代价是要自己实现一套 IO 回调。4.1 minizip 的 IO 抽象zlib_filefunc64_def 七个回调minizip 内部所有文件操作都走zlib_filefunc64_def这个函数指针表默认实现是fill_fopen_filefunc64基于 fopen/fread。这张表有七个槽位语义和 C 标准 IO 基本对应回调对应操作注意事项zopen64_filefopen返回的句柄会传给其余所有回调zread_filefread返回实际读到的字节数读完返回 0zwrite_filefwrite返回实际写入字节数失败返回 0ztell64_fileftell返回 64 位偏移zseek64_filefseekorigin 用ZLIB_FILEFUNC_SEEK_*宏值 0/1/2zclose_filefclose只释放句柄本身别把数据 buffer 也释放了zerror_fileferror返回非 0 表示该流出过错opaque字段是透传的你可以在里面塞任意上下文指针七个回调都能拿到。这一点是整套改造的关键——用它把内存缓冲区带进去。4.2 实现一套内存缓冲区回调// mem_io.cpp : 给 minizip 换一套基于内存缓冲区的 IO 回调 #include cstdlib #include cstring #include cstdint extern C { #include zlib.h #include ioapi.h #include zip.h #include unzip.h } struct MemStream { unsigned char *data nullptr; // 缓冲区所有权归调用方 size_t size 0; // 有效数据长度 size_t cap 0; // 已分配容量 size_t pos 0; // 当前读写位置 int err 0; }; struct MemFile { MemStream *ms nullptr; }; static voidpf ZCALLBACK mem_open64(voidpf opaque, const void *, int mode) { MemStream *ms static_castMemStream *(opaque); MemFile *mf static_castMemFile *(calloc(1, sizeof(MemFile))); if (!mf) return nullptr; mf-ms ms; ms-pos 0; // 写模式要清空长度读模式保留原有数据 if (mode ZLIB_FILEFUNC_MODE_CREATE) ms-size 0; return mf; } static long ZCALLBACK mem_read(voidpf, voidpf stream, void *buf, uLong size) { MemFile *mf static_castMemFile *(stream); MemStream *ms mf-ms; size_t left ms-size ms-pos ? ms-size - ms-pos : 0; size_t n size left ? (size_t)size : left; if (n) { memcpy(buf, ms-data ms-pos, n); ms-pos n; } return (long)n; } static long ZCALLBACK mem_write(voidpf, voidpf stream, const void *buf, uLong size) { MemFile *mf static_castMemFile *(stream); MemStream *ms mf-ms; size_t need ms-pos (size_t)size; if (need ms-cap) { size_t ncap ms-cap ? ms-cap : 64 * 1024; while (ncap need) ncap 1; // 翻倍扩容避免频繁 realloc unsigned char *p static_castunsigned char *(realloc(ms-data, ncap)); if (!p) { ms-err 1; return 0; } ms-data p; ms-cap ncap; } memcpy(ms-data ms-pos, buf, (size_t)size); ms-pos size; if (ms-pos ms-size) ms-size ms-pos; return (long)size; } static ZPOS64_T ZCALLBACK mem_tell64(voidpf, voidpf stream) { return static_castMemFile *(stream)-ms-pos; } static long ZCALLBACK mem_seek64(voidpf, voidpf stream, ZPOS64_T offset, int origin) { MemStream *ms static_castMemFile *(stream)-ms; ZPOS64_T base 0; if (origin ZLIB_FILEFUNC_SEEK_CUR) base ms-pos; else if (origin ZLIB_FILEFUNC_SEEK_END) base ms-size; ZPOS64_T np base offset; if (np (ZPOS64_T)SIZE_MAX) return -1; ms-pos (size_t)np; return 0; } static int ZCALLBACK mem_close(voidpf, voidpf stream) { free(stream); // 只释放 MemFiledata 留给调用方 return 0; } static int ZCALLBACK mem_error(voidpf, voidpf stream) { return static_castMemFile *(stream)-ms-err; } static void fill_mem_func(zlib_filefunc64_def f, MemStream *ms) { memset(f, 0, sizeof(f)); f.zopen64_file mem_open64; f.zread_file mem_read; f.zwrite_file mem_write; f.ztell64_file mem_tell64; f.zseek64_file mem_seek64; f.zclose_file mem_close; f.zerror_file mem_error; f.opaque ms p a hrefhttps://download.csdn.net/download/cola8_jun/11037682 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p

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

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

免费获取报价 →
↑