1、C++ 使用ZLIB库中的MiniZip实现递归目录压缩

Zlib是一个开源的数据压缩库,提供了一种通用的数据压缩和解压缩算法。它最初由Jean-Loup GaillyMark Adler开发,旨在成为一个高效、轻量级的压缩库,其被广泛应用于许多领域,包括网络通信、文件压缩、数据库系统等。其压缩算法是基于DEFLATE算法,这是一种无损数据压缩算法,通常能够提供相当高的压缩比。

Zlib项目中的contrib目录下有一个minizip子项目,minizip实际上不是zlib库的一部分,而是一个独立的开源库,用于处理ZIP压缩文件格式。它提供了对ZIP文件的创建和解压的简单接口。minizip在很多情况下与zlib一起使用,因为ZIP压缩通常使用了DEFLATE压缩算法。通过对minizip库的二次封装则可实现针对目录的压缩与解压功能。

在这里插入图片描述
如果你想使用minizip通常你需要下载并编译它,然后将其链接到你的项目中。

编译Zlib库很简单,解压文件并进入到**\zlib-1.3\contrib\vstudio目录下,根据自己编译器版本选择不同的目录,这里我选择vc12**,进入后打开zlibvc.sln等待生成即可。
在这里插入图片描述

在这里插入图片描述
成功后可获得两个文件分别是zlibstat.libzlibwapi.lib如下图;
在这里插入图片描述
接着配置引用目录,这里需要多配置一个minizip头文件,该头文件是zlib里面的一个子项目。
在这里插入图片描述
lib库则需要包含zlibstat.libzlibwapi.lib这两个文件,此处可以自行放入到一个目录下;

在这里插入图片描述
在这里插入图片描述
ZIP 递归压缩目录:

  • 如下所示代码是一个使用zlib库实现的简单文件夹压缩工具的C++程序。该程序提供了压缩文件夹到 ZIP 文件的功能,支持递归地添加文件和子文件夹,利用了 Windows API 和 zlib 库的函数。
#define ZLIB_WINAPI
#include <string>
#include <iostream>
#include <vector>
#include <Shlwapi.h> 
#include <zip.h>
#include <unzip.h>
#include <zlib.h>

using namespace std;

#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "zlibstat.lib")

bool AddfiletoZip(zipFile zfile, const std::string& fileNameinZip, const std::string& srcfile)
{
	// 目录如果为空则直接返回
	if (NULL == zfile || fileNameinZip.empty())
	{
		return 0;
	}

	int nErr = 0;
	zip_fileinfo zinfo = { 0 };
	tm_zip tmz = { 0 };
	zinfo.tmz_date = tmz;
	zinfo.dosDate = 0;
	zinfo.internal_fa = 0;
	zinfo.external_fa = 0;

	char sznewfileName[MAX_PATH] = { 0 };
	memset(sznewfileName, 0x00, sizeof(sznewfileName));
	strcat_s(sznewfileName, fileNameinZip.c_str());
	if (srcfile.empty())
	{
		strcat_s(sznewfileName, "\\");
	}

	nErr = zipOpenNewFileInZip(zfile, sznewfileName, &zinfo, NULL, 0, NULL, 0, NULL, Z_DEFLATED, Z_DEFAULT_COMPRESSION);
	if (nErr != ZIP_OK)
	{
		return false;
	}
	if (!srcfile.empty())
	{
		// 打开源文件
		FILE* srcfp = _fsopen(srcfile.c_str(), "rb", _SH_DENYNO);
		if (NULL == srcfp)
		{
			std::cout << "打开源文件失败" << std::endl;
			return false;
		}

		// 读入源文件写入zip文件
		int numBytes = 0;
		char* pBuf = new char[1024 * 100];
		if (NULL == pBuf)
		{
			std::cout << "新建缓冲区失败" << std::endl;
			return 0;
		}
		while (!feof(srcfp))
		{
			memset(pBuf, 0x00, sizeof(pBuf));
			numBytes = fread(pBuf, 1, sizeof(pBuf), srcfp);
			nErr = zipWriteInFileInZip(zfile, pBuf, numBytes);
			if (ferror(srcfp))
			{
				break;
			}
		}
		delete[] pBuf;
		fclose(srcfp);
	}
	zipCloseFileInZip(zfile);

	return true;
}

bool CollectfileInDirtoZip(zipFile zfile, const std::string& filepath, const std::string& parentdirName)
{
	if (NULL == zfile || filepath.empty())
	{
		return false;
	}
	bool bFile = false;
	std::string relativepath = "";
	WIN32_FIND_DATAA findFileData;

	char szpath[MAX_PATH] = { 0 };
	if (::PathIsDirectoryA(filepath.c_str()))
	{
		strcpy_s(szpath, sizeof(szpath) / sizeof(szpath[0]), filepath.c_str());
		int len = strlen(szpath) + strlen("\\*.*") + 1;
		strcat_s(szpath, len, "\\*.*");
	}
	else
	{
		bFile = true;
		strcpy_s(szpath, sizeof(szpath) / sizeof(szpath[0]), filepath.c_str());
	}

	HANDLE hFile = ::FindFirstFileA(szpath, &findFileData);
	if (NULL == hFile)
	{
		return false;
	}
	do
	{
		if (parentdirName.empty())
			relativepath = findFileData.cFileName;
		else
			// 生成zip文件中的相对路径
			relativepath = parentdirName + "\\" + findFileData.cFileName;

		// 如果是目录
		if (findFileData.dwFileAttributes == FILE_ATTRIBUTE_DIRECTORY)
		{
			// 去掉目录中的.当前目录和..前一个目录
			if (strcmp(findFileData.cFileName, ".") != 0 && strcmp(findFileData.cFileName, "..") != 0)
			{
				nyAddfiletoZip(zfile, relativepath, "");

				char szTemp[MAX_PATH] = { 0 };
				strcpy_s(szTemp, filepath.c_str());
				strcat_s(szTemp, "\\");
				strcat_s(szTemp, findFileData.cFileName);
				nyCollectfileInDirtoZip(zfile, szTemp, relativepath);
			}
			continue;
		}
		char szTemp[MAX_PATH] = { 0 };
		if (bFile)
		{
			//注意:处理单独文件的压缩
			strcpy_s(szTemp, filepath.c_str());
		}
		else
		{
			//注意:处理目录文件的压缩
			strcpy_s(szTemp, filepath.c_str());
			strcat_s(szTemp, "\\");
			strcat_s(szTemp, findFileData.cFileName);
		}

		nyAddfiletoZip(zfile, relativepath, szTemp);

	} while (::FindNextFileA(hFile, &findFileData));
	FindClose(hFile);

	return true;
}

/*
* 函数功能 : 压缩文件夹到目录
* 备    注 : dirpathName 源文件/文件夹
*      zipFileName 目的压缩包
*      parentdirName 压缩包内名字(文件夹名)
*/
bool CreateZipfromDir(const std::string& dirpathName, const std::string& zipfileName, const std::string& parentdirName)
{
	bool bRet = false;

	/*
	APPEND_STATUS_CREATE    创建追加
	APPEND_STATUS_CREATEAFTER 创建后追加(覆盖方式)
	APPEND_STATUS_ADDINZIP    直接追加
	*/
	zipFile zFile = NULL;
	if (!::PathFileExistsA(zipfileName.c_str()))
	{
		zFile = zipOpen(zipfileName.c_str(), APPEND_STATUS_CREATE);
	}
	else
	{
		zFile = zipOpen(zipfileName.c_str(), APPEND_STATUS_ADDINZIP);
	}
	if (NULL == zFile)
	{
		std::cout << "创建ZIP文件失败" << std::endl;
		return bRet;
	}

	if (nyCollectfileInDirtoZip(zFile, dirpathName, parentdirName))
	{
		bRet = true;
	}

	zipClose(zFile, NULL);

	return bRet;
}

int main(int argc, char* argv[])
{
	std::string dirpath = "F:\\vs2013_code\\ConsoleApplication2\\Debug\\1.txt";                   // 源文件/文件夹
	std::string zipfileName = "F:\\vs2013_code\\ConsoleApplication2\\Debug\\test.zip";           // 目的压缩包

	//bool ref = nyCreateZipfromDir(dirpath, zipfileName, "lyshark");          // 包内文件名<如果为空则压缩时不指定目录>
	bool ref = nyCreateZipfromDir(dirpath, zipfileName, "a");          // 包内文件名<如果为空则压缩时不指定目录>

	std::cout << "[LyShark] 压缩状态 " << ref << std::endl;
	system("pause");
	return 0;
}
  • 主要功能

    • CreateZipfromDir函数:

      • bool CreateZipfromDir(const std::string& dirpathName, const std::string& zipfileName, const std::string& parentdirName);
      • 功能:压缩文件夹到指定的 ZIP 文件。
        • 参数:
          • dirpathName:源文件夹路径。
          • zipfileName:目标 ZIP 文件路径。
          • parentdirName:在 ZIP 文件内的文件夹名(如果为空则不指定目录)。
    • CollectfileInDirtoZip 函数:

      • bool CollectfileInDirtoZip(zipFile zfile, const std::string& filepath, const std::string& parentdirName);
        • 功能:递归地收集文件夹中的文件,并将它们添加到已打开的 ZIP 文件中。
          • 参数:
            • zfile:已打开的 ZIP 文件。
            • filepath:文件夹路径。
            • parentdirName:在 ZIP 文件内的相对文件夹名。
    • AddfiletoZip 函数:

      • bool AddfiletoZip(zipFile zfile, const std::string& fileNameinZip, const std::string& srcfile);
        • 功能:将指定文件添加到已打开的 ZIP 文件中。
          • 参数:
            • zfile:已打开的 ZIP 文件。
            • fileNameinZip:在 ZIP 文件内的相对文件路径。
            • srcfile:源文件路径。
  • 程序流程:

    • 文件夹压缩参数设置: 用户提供源文件夹路径、目标 ZIP 文件路径,以及在 ZIP 文件内的文件夹名。
    • ZIP 文件打开: 根据目标 ZIP 文件是否存在,使用 zipOpen 函数打开 ZIP 文件。
    • 文件夹递归添加: 使用 CollectfileInDirtoZip 函数递归地收集文件夹中的文件,并通过 AddfiletoZip 函数将它们添加到 ZIP 文件中。
    • ZIP 文件关闭: 使用 zipClose 函数关闭 ZIP 文件。
  • 示例用法

int main(int argc, char* argv[])
{
	std::string dirpath = "F:\\vs2013_code\\ConsoleApplication2\\Debug\\1.txt";                   // 源文件/文件夹
	std::string zipfileName = "F:\\vs2013_code\\ConsoleApplication2\\Debug\\test.zip";           // 目的压缩包

	//bool ref = nyCreateZipfromDir(dirpath, zipfileName, "lyshark");          // 包内文件名<如果为空则压缩时不指定目录>
	bool ref = nyCreateZipfromDir(dirpath, zipfileName, "a");          // 包内文件名<如果为空则压缩时不指定目录>

	std::cout << "[LyShark] 压缩状态 " << ref << std::endl;
	system("pause");
	return 0;
}
  • 上述调用代码,参数1指定为需要压缩的文件目录,参数2指定为需要压缩成目录名,参数3为压缩后该目录的名字。
    在这里插入图片描述

2、C++ 使用ZLIB库中的MiniZip实现递归目录解压缩

  • 在这个C++程序中,实现了递归解压缩ZIP文件的功能。程序提供了以下主要功能:
    • replace_all 函数: 用于替换字符串中的指定子串。
    • CreatedMultipleDirectory 函数: 用于创建多级目录,确保解压缩时的目录结构存在。
    • UnzipFile 函数: 用于递归解压缩 ZIP 文件。该函数打开 ZIP 文件,获取文件信息,然后逐个解析和处理 ZIP 文件中的文件或目录:
#define ZLIB_WINAPI
#include <string>
#include <iostream>
#include <vector>
#include <Shlwapi.h> 
#include <zip.h>
#include <unzip.h>
#include <zlib.h>

using namespace std;

#pragma comment(lib, "Shlwapi.lib")
#pragma comment(lib, "zlibstat.lib")

// 将字符串内的old_value替换成new_value
std::string& replace_all(std::string& str, const std::string& old_value, const std::string& new_value)
{
	while (true)
	{
		std::string::size_type pos(0);
		if ((pos = str.find(old_value)) != std::string::npos)
			str.replace(pos, old_value.length(), new_value);
		else
			break;
	}
	return str;
}

// 创建多级目录
BOOL CreatedMultipleDirectory(const std::string& direct)
{
	std::string Directoryname = direct;
	if (Directoryname[Directoryname.length() - 1] != '\\')
	{
		Directoryname.append(1, '\\');
	}
	std::vector< std::string> vpath;
	std::string strtemp;
	BOOL  bSuccess = FALSE;
	for (int i = 0; i < Directoryname.length(); i++)
	{
		if (Directoryname[i] != '\\')
		{
			strtemp.append(1, Directoryname[i]);
		}
		else
		{
			vpath.push_back(strtemp);
			strtemp.append(1, '\\');
		}
	}
	std::vector< std::string>::iterator vIter = vpath.begin();
	for (; vIter != vpath.end(); vIter++)
	{
		bSuccess = CreateDirectoryA(vIter->c_str(), NULL) ? TRUE : FALSE;
	}
	return bSuccess;
}

/*
* 函数功能 : 递归解压文件目录
* 备    注 : strFilePath 压缩包路径
*      strTempPath 解压到
*/
void UnzipFile(const std::string& strFilePath, const std::string& strTempPath)
{
	int nReturnValue;
	string tempFilePath;
	string srcFilePath(strFilePath);
	string destFilePath;

	// 打开zip文件
	unzFile unzfile = unzOpen(srcFilePath.c_str());
	if (unzfile == NULL)
	{
		return;
	}

	// 获取zip文件的信息
	unz_global_info* pGlobalInfo = new unz_global_info;
	nReturnValue = unzGetGlobalInfo(unzfile, pGlobalInfo);
	if (nReturnValue != UNZ_OK)
	{
		std::cout << "数据包: " << pGlobalInfo->number_entry << endl;
		return;
	}

	// 解析zip文件
	unz_file_info* pFileInfo = new unz_file_info;
	char szZipFName[MAX_PATH] = { 0 };
	char szExtraName[MAX_PATH] = { 0 };
	char szCommName[MAX_PATH] = { 0 };

	// 存放从zip中解析出来的内部文件名
	for (int i = 0; i < pGlobalInfo->number_entry; i++)
	{
		// 解析得到zip中的文件信息
		nReturnValue = unzGetCurrentFileInfo(unzfile, pFileInfo, szZipFName, MAX_PATH, szExtraName, MAX_PATH, szCommName, MAX_PATH);
		if (nReturnValue != UNZ_OK)
			return;

		std::cout << "解压文件名: " << szZipFName << endl;

		string strZipFName = szZipFName;

		// 如果是目录则执行创建递归目录名
		if (pFileInfo->external_fa == FILE_ATTRIBUTE_DIRECTORY || (strZipFName.rfind('/') == strZipFName.length() - 1))
		{
			destFilePath = strTempPath + "//" + szZipFName;
			CreateDirectoryA(destFilePath.c_str(), NULL);
		}

		// 如果是文件则解压缩并创建
		else
		{
			// 创建文件 保存完整路径
			string strFullFilePath;
			tempFilePath = strTempPath + "/" + szZipFName;
			strFullFilePath = tempFilePath;

			int nPos = tempFilePath.rfind("/");
			int nPosRev = tempFilePath.rfind("\\");
			if (nPosRev == string::npos && nPos == string::npos)
				continue;

			size_t nSplitPos = nPos > nPosRev ? nPos : nPosRev;
			destFilePath = tempFilePath.substr(0, nSplitPos + 1);

			if (!PathIsDirectoryA(destFilePath.c_str()))
			{
				// 将路径格式统一
				destFilePath = replace_all(destFilePath, "/", "\\");
				// 创建多级目录
				int bRet = CreatedMultipleDirectory(destFilePath);
			}
			strFullFilePath = replace_all(strFullFilePath, "/", "\\");

			HANDLE hFile = CreateFileA(strFullFilePath.c_str(), GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL);
			if (hFile == INVALID_HANDLE_VALUE)
			{
				return;
			}

			// 打开文件
			nReturnValue = unzOpenCurrentFile(unzfile);
			if (nReturnValue != UNZ_OK)
			{
				CloseHandle(hFile);
				return;
			}

			// 读取文件
			uLong BUFFER_SIZE = pFileInfo->uncompressed_size;;
			void* szReadBuffer = NULL;
			szReadBuffer = (char*)malloc(BUFFER_SIZE);
			if (NULL == szReadBuffer)
			{
				break;
			}

			while (TRUE)
			{
				memset(szReadBuffer, 0, BUFFER_SIZE);
				int nReadFileSize = 0;

				nReadFileSize = unzReadCurrentFile(unzfile, szReadBuffer, BUFFER_SIZE);

				// 读取文件失败
				if (nReadFileSize < 0)
				{
					unzCloseCurrentFile(unzfile);
					CloseHandle(hFile);
					return;
				}
				// 读取文件完毕
				else if (nReadFileSize == 0)
				{
					unzCloseCurrentFile(unzfile);
					CloseHandle(hFile);
					break;
				}
				// 写入读取的内容
				else
				{
					DWORD dWrite = 0;
					BOOL bWriteSuccessed = WriteFile(hFile, szReadBuffer, BUFFER_SIZE, &dWrite, NULL);
					if (!bWriteSuccessed)
					{
						unzCloseCurrentFile(unzfile);
						CloseHandle(hFile);
						return;
					}
				}
			}
			free(szReadBuffer);
		}
		unzGoToNextFile(unzfile);
	}

	delete pFileInfo;
	delete pGlobalInfo;

	// 关闭
	if (unzfile)
	{
		unzClose(unzfile);
	}
}
  • 主要功能
    • replace_all 函数

      • std::string& replace_all(std::string& str, const std::string& old_value, const std::string& new_value)
      • 功能:在字符串 str 中替换所有的 old_value 为 new_value。
        • 参数:
          • str:待处理的字符串。
          • old_value:要被替换的子串。
          • new_value:替换后的新子串。
          • 返回值:替换后的字符串。
    • CreatedMultipleDirectory 函数

      • BOOL CreatedMultipleDirectory(const std::string& direct)
      • 功能:创建多级目录,确保路径存在。
        • 参数:
          • direct:目录路径。
          • 返回值:如果成功创建目录返回 TRUE,否则返回 FALSE。
    • UnzipFile 函数

      • void UnzipFile(const std::string& strFilePath, const std::string& strTempPath)
      • 功能:递归解压缩 ZIP 文件。
        • 参数:
          • strFilePath:ZIP 文件路径。
          • strTempPath:解压到的目标路径。
          • 该函数打开 ZIP 文件,获取文件信息,然后逐个解析和处理 ZIP 文件中的文件或目录。在解析过程中,根据文件或目录的属性,创建相应的目录结构,然后将文件写入目标路径。

示例用法:

int main(int argc, char* argv[])
{
	std::string srcFilePath = "D:\\lyshark\\test.zip";
	std::string tempdir = "D:\\lyshark\\test";

	// 如果传入目录不存在则创建
	if (!::PathFileExistsA(tempdir.c_str()))
	{
		CreatedMultipleDirectory(tempdir);
	}

	// 调用解压函数
	UnzipFile(srcFilePath, tempdir);

	system("pause");
	return 0;
}

案例中,首先在解压缩之前判断传入目录是否存在,如果不存在则需要调用API创建目录,如果存在则直接调用UnzipFIle解压缩函数,实现解包,输出效果图如下;
在这里插入图片描述

3、使用过程遇到的问题

MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _rand 已经在 LIBCMT.lib(rand.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _srand 已经在 LIBCMT.lib(rand.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _free 已经在 LIBCMT.lib(free.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _malloc 已经在 LIBCMT.lib(malloc.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: __time64 已经在 LIBCMT.lib(time64.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _fclose 已经在 LIBCMT.lib(fclose.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _ferror 已经在 LIBCMT.lib(feoferr.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _fopen 已经在 LIBCMT.lib(fopen.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _fread 已经在 LIBCMT.lib(fread.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _fseek 已经在 LIBCMT.lib(fseek.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _ftell 已经在 LIBCMT.lib(ftell.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: __fseeki64 已经在 LIBCMT.lib(fseeki64.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: __ftelli64 已经在 LIBCMT.lib(ftelli64.obj) 中定义
1>MSVCRTD.lib(MSVCR90D.dll) : error LNK2005: _fwrite 已经在 LIBCMT.lib(fwrite.obj) 中定义
1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: __thiscall type_info::type_info(class type_info const &)" (??0type_info@@AAE@ABV0@@Z) 已经在 LIBCMT.lib(typinfo.obj) 中定义
1>MSVCRTD.lib(ti_inst.obj) : error LNK2005: "private: class type_info & __thiscall type_info::operator=(class type_info const &)" (??4type_info@@AAEAAV0@ABV0@@Z) 已经在 LIBCMT.lib(typinfo.obj) 中定义
1>   正在创建库 Release\sdologin.lib 和对象 Release\sdologin.exp
1>LINK : warning LNK4098: 默认库“MSVCRTD”与其他库的使用冲突;请使用 /NODEFAULTLIB:library
  • 遇到的错误是因为项目同时引用了两种不同的C运行时库(CRT):动态链接的调试库 MSVCRTD.lib 和静态链接的多线程库 LIBCMT.lib。这种冲突通常会在链接时导致 LNK2005 错误,因为同一个符号在两个库中都被定义了。

  • 要解决这个问题,你需要确保项目整体上只使用一种类型的CRT库。通常,解决方案涉及以下步骤:

    • 步骤 1: 确认项目的CRT设置:
      • 打开项目属性(右键项目 -> 属性)。
      • 导航到 配置属性 -> C/C++ -> 代码生成 -> 运行库。
      • 选择适当的运行库选项:
      • 对于调试版本,通常选择 多线程调试DLL (/MDd)。
      • 对于发布版本,选择 多线程DLL (/MD)。
      • 确保所有项目配置(Debug、Release等)都正确设置。

在这里插入图片描述
在这里插入图片描述

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐