• C++ MiniZip实现目录压缩与解压


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

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

    如果你想使用minizip通常你需要下载并编译它,然后将其链接到你的项目中。

    • Zlib源码:https://download.csdn.net/download/lyshark_csdn/88561117

    编译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 
    #include 
    #include 
    #include  
    #include 
    #include 
    #include 
    
    using namespace std;
    
    #pragma comment(lib, "Shlwapi.lib")
    #pragma comment(lib, "zlibstat.lib")
    
    bool nyAddfiletoZip(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 nyCollectfileInDirtoZip(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 nyCreateZipfromDir(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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    主要功能

    nyCreateZipfromDir函数

    bool nyCreateZipfromDir(const std::string& dirpathName, const std::string& zipfileName, const std::string& parentdirName);
    
    • 1

    功能:压缩文件夹到指定的 ZIP 文件。

    参数:

    • dirpathName:源文件夹路径。
    • zipfileName:目标 ZIP 文件路径。
    • parentdirName:在 ZIP 文件内的文件夹名(如果为空则不指定目录)。

    nyCollectfileInDirtoZip 函数

    bool nyCollectfileInDirtoZip(zipFile zfile, const std::string& filepath, const std::string& parentdirName);
    
    • 1

    功能:递归地收集文件夹中的文件,并将它们添加到已打开的 ZIP 文件中。

    参数:

    • zfile:已打开的 ZIP 文件。
    • filepath:文件夹路径。
    • parentdirName:在 ZIP 文件内的相对文件夹名。

    nyAddfiletoZip 函数

    bool nyAddfiletoZip(zipFile zfile, const std::string& fileNameinZip, const std::string& srcfile);
    
    • 1

    功能:将指定文件添加到已打开的 ZIP 文件中。

    参数:

    • zfile:已打开的 ZIP 文件。
    • fileNameinZip:在 ZIP 文件内的相对文件路径。
    • srcfile:源文件路径。

    程序流程

    • 文件夹压缩参数设置: 用户提供源文件夹路径、目标 ZIP 文件路径,以及在 ZIP 文件内的文件夹名。
    • ZIP 文件打开: 根据目标 ZIP 文件是否存在,使用 zipOpen 函数打开 ZIP 文件。
    • 文件夹递归添加: 使用 nyCollectfileInDirtoZip 函数递归地收集文件夹中的文件,并通过 nyAddfiletoZip 函数将它们添加到 ZIP 文件中。
    • ZIP 文件关闭: 使用 zipClose 函数关闭 ZIP 文件。
    示例用法
    int main(int argc, char* argv[])
    {
    	std::string dirpath = "D:\\lyshark\\test";                   // 源文件/文件夹
    	std::string zipfileName = "D:\\lyshark\\test.zip";           // 目的压缩包
    	
    	bool ref = nyCreateZipfromDir(dirpath, zipfileName, "lyshark");          // 包内文件名<如果为空则压缩时不指定目录>
    
    	std::cout << "[LyShark] 压缩状态 " << ref << std::endl;
    	system("pause");
    	return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    上述调用代码,参数1指定为需要压缩的文件目录,参数2指定为需要压缩成目录名,参数3为压缩后该目录的名字。

    ZIP 递归解压目录

    在这个C++程序中,实现了递归解压缩ZIP文件的功能。程序提供了以下主要功能:

    • replace_all 函数: 用于替换字符串中的指定子串。
    • CreatedMultipleDirectory 函数: 用于创建多级目录,确保解压缩时的目录结构存在。
    • UnzipFile 函数: 用于递归解压缩 ZIP 文件。该函数打开 ZIP 文件,获取文件信息,然后逐个解析和处理 ZIP 文件中的文件或目录。
    #define ZLIB_WINAPI
    #include 
    #include 
    #include 
    #include  
    #include 
    #include 
    #include 
    
    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);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    主要功能

    replace_all 函数

    std::string& replace_all(std::string& str, const std::string& old_value, const std::string& new_value)
    
    • 1

    功能:在字符串 str 中替换所有的 old_value 为 new_value。

    参数:

    • str:待处理的字符串。
    • old_value:要被替换的子串。
    • new_value:替换后的新子串。

    返回值:替换后的字符串。

    CreatedMultipleDirectory 函数

    BOOL CreatedMultipleDirectory(const std::string& direct)
    
    • 1

    功能:创建多级目录,确保路径存在。

    参数:

    • direct:目录路径。
    • 返回值:如果成功创建目录返回 TRUE,否则返回 FALSE。

    UnzipFile 函数

    void UnzipFile(const std::string& strFilePath, const std::string& strTempPath)
    
    • 1

    功能:递归解压缩 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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

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

  • 相关阅读:
    微信扫码登陆流程
    [Qt]QMainWindow
    手机便签功能在哪里?如何在便签里添加文字图片视频?
    SPARKSQL3.0-Optimizer阶段源码剖析
    支付宝周期扣款(支付后签约)业务功能总结(php+golang)
    JavaSE 集合类详解
    【运维】fstab,systemctl与rc.local启动顺序
    视频监控系统/视频云存储EasyCVR接入国标GB28181设备无法播放设备录像,是什么原因?
    STM32CubeMX教程29 USB_HOST - 使用FatFs文件系统读写U盘
    Linux 进程间通信
  • 原文地址:https://blog.csdn.net/lyshark_csdn/article/details/134557147