65.9K
CodeProject 正在变化。 阅读更多。
Home

CAB文件(*.CAB)压缩与解压缩

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.93/5 (208投票s)

2006年8月29日

CPOL

38分钟阅读

viewsIcon

3359941

downloadIcon

26669

如何实现 Microsoft CAB 文件的创建和提取

引言

使用此项目,C++.NET程序员可以获得一个非常通用的MicrosoftCAB文件的压缩和解压缩库。

.NET 1.1不提供压缩功能。.NET 2.0提供了System.IO.Compression.GZipStream类。但这使用起来很麻烦,而且非常原始;它只能压缩流,但无法压缩包含文件和子文件夹的文件夹。

如果您在互联网上搜索更方便的压缩库,您会发现,例如ICSharpCode.SharpZipLib.dll,它提供ZIP压缩。但这个库使用起来很麻烦,而且有bug,因此无法使用。尽管这些bug存在多年,但作者并未修复。

我问自己,为什么我要寻找另一个(又会有其他bug的)开源库,而Windows本身从一开始就支持CAB档案?Microsoft的Cabinet.dll(位于System32目录中)没有bug。许多Microsoft安装程序(如Internet Explorer或Windows更新的安装程序)都使用它。此外,CAB的压缩率远高于ZIP。

特点

  • 这个库使用起来非常容易
  • 这个库轻量且非常快速。(纯C++代码)
  • 这个库可以非常容易地扩展
  • 一个项目是为C++开发人员准备的。
  • 另一个项目是为.NET开发人员准备的。
  • 这两个项目都可以在Visual Studio 2005和2010上编译
  • .NET项目可以在安装了所需.NET框架的任何平台上运行
  • 两个项目都支持多线程
  • 可选的CAB文件加密/解密。
  • CAB文件可以包含文件夹和文件的树形结构
  • 压缩/解压缩时会保留文件日期UTC本地时间)和文件属性
  • 解压缩嵌入在Win32项目或.NET项目资源中的CAB文件。
  • 在.NET中,还可以从中解压缩CAB文件。
  • 压缩可以大CAB文件分割成多个部分。(Pack1.cab, Pack2.cab, Pack3.cab等)
  • 一个事件处理程序允许在应用程序的GUI中显示压缩/解压缩的进度
  • 在压缩和解压缩过程中会调用很多事件处理程序,允许与进度进行交互(例如过滤特定文件)。
  • 两个项目都附带一个演示应用程序,展示如何压缩和解压缩文件以及嵌入的CAB资源。演示中还包括加密和解密。
  • 此项目利用您System(32)目录中的MicrosoftCabinet.dll,该文件自Windows NT/98起就是操作系统的一部分。
  • Cabinet.dll仅在需要时加载,之后卸载。您也可以静态链接,则不需要外部DLL。
  • 下载中包含适用于VS 2003、2005、2008 x32和x64的CabLib.dll发布版本
  • C++和.NET项目都支持路径和文件名Unicode(例如日语),无论编译为MBCS还是UNICODE
  • .NET库是强命名的,可以使用gacutil.exe安装到GAC中。

版本历史

  • 自2007年1月版本起,不再需要Msvcp70/71/80.DLL
  • 自2008年4月版本起
    • 您可以直接从服务器解压缩CAB文件(URL从HTTP(S)/FTP提取)。
    • 您甚至可以只从服务器上的CAB文件中提取特定文件,而无需下载整个CAB文件(部分下载)。
    • 您还可以滥用此库仅从互联网下载文件(MP3、AVI等)到磁盘,而无需CAB解压缩。
  • 自2008年5月版本起,.NET库还可以解压缩扩展名为.URL.LNK的文件。
  • 自2008年6月版本起,CAB文件的加密/解密使用Blowfish算法。(见下文)
  • 自2008年8月版本起,支持无压缩创建CAB档案。
  • 自2008年9月版本起,您可以直接提取到内存。此外,UTF7已替换为UTF8
  • 自2008年10月版本起,两个项目都可以编译为32位或64位版本
  • 自2009年1月版本起,您可以关闭UTF8编码。(见下文!)
  • 自2009年2月版本起,可以静态链接Cabinet.dll。(见Defines.h)
  • 自2009年3月版本起,CabLib.dll已强命名
  • 自2009年8月版本起,两个项目都支持多线程
  • 自2009年12月版本起
    • 新的回调显示提取大文件时的进度
    • 分割的CAB文件可以作为资源嵌入,
    • 一个bug修复允许在提取分割CAB时跳过文件,
    • 另一个bug修复纠正了默认压缩临时目录,
    • 添加了用于Visual Studio 2008的解决方案和发布DLL
  • 自2010年2月版本起,已修复一个关于提取某些分割CAB文件的bug,并添加了LZX压缩。
  • 自2010年3月版本起,已添加几个错误消息,以防库被错误使用。支持提取非标准格式的CAB文件。

  • 自2012年3月版本起,已修复一个关于从HTTPS服务器提取URL的bug
  • 自2016年1月版本起,项目可在Visual Studio 14及更高版本上编译。(Microsoft已移除/clr:oldsyntax选项)

您可以下载我的通用安装程序(含源代码),该安装程序使用CabLib库进行软件安装和更新。
此应用程序演示了所有可用的CabLib功能,如URL提取、内存提取、如何在进度条中显示进度等。

依赖项

Cabinet / CabLib 项目 依赖项 框架 C++ 可再发行组件
Cabinet C++ 项目 - 32 位
VS 6, 2002, 2003, 2005, 2010
none none none
Cabinet C++ 项目 - 64 位
VS 2005, 2010
none none none
CabLib .NET 项目 - 32 位
VS 2005
MsVcr80.DLL, MsVcm80.DLL (x32) 2.0 2005 x86 下载
CabLib .NET 项目 - 64 位
VS 2005
MsVcr80.DLL, MsVcm80.DLL (Amd64) 2.0 2005 x64 下载
CabLib .NET 项目 - 32 位
VS 2010
MsVcr100.DLL (x32) 4.X 2010 x86 下载
CabLib .NET 项目 - 64 位
VS 2010
MsVcr100.DLL (Amd64) 4.X 2010 x64 下载

Microsoft移除了VS 2005后不再提供的VS 2003功能
/MT编译器开关不再适用于托管C++项目。(错误 D8016)
在VS 2003上,此开关将C++函数(fopen, fread, flose等)静态链接到CabLib.dll中。
在VS 2005/2008上,这已不再可能,因此还需要外部DLL MSVCR80/90.DLL和MSVCM80/90.DLL。

注意:
如果目标机器上未安装这些DLL,用户将不会收到智能错误消息来告知他们问题所在。相反,Microsoft .NET会抛出非常愚蠢的异常,例如:“无法加载CabLib.dll或其依赖项之一”或“System.IO.FileLoadException:....应用程序启动失败,因为应用程序配置不正确。重新安装应用程序可能可以解决此问题”。您的应用程序用户将永远不会怀疑原因是缺少MsVcr80/90.dll!

自VS 2005起,一个清单文件(XML)会自动生成并编译到CabLib.dll中(RT_MANIFEST),告知它依赖于哪些DLL。您可以使用ResourceHacker打开CabLib.DLL来查看此清单。

有两种方法可以确保目标计算机上存在MsVcr/MsVcm DLL

  1. 您的应用程序的用户必须下载并安装正确版本的Visual C++ Redistributable Package(参见上表中的下载链接)。文件将作为共享程序集“并排”安装到C:\Windows\WinSxS\Cryptic Folder。
  2. 或者,您可以将以下文件直接安装到应用程序文件夹中作为私有程序集:Microsoft.VC80.CRT.manifest, msvcr80.dll, msvcm80.dll。您可以在Visual Studio文件夹下的VC\Redist中找到这些文件。这些DLL的版本必须与编译到CabLib.dll中的清单中的版本完全相同。您可以在MSDN中阅读以下文章:Private AssembliesAssembly Searching Sequence,但您很可能会感到非常困惑。您可以在tydbits上找到一篇更好的文章。

限制

  • Cabinet.dll可以压缩的最大文件大小为2 GB。
  • 单个CAB文件的最大大小也为2 GB。但您可以创建无限大小的分割CAB文件。(Part1.cab, Part2.cab,见下文)
  • 此项目无法压缩或解压缩InstallShield CAB文件。(见下文)
  • 您无法向现有CAB档案添加文件或从中删除文件。
  • 对于Windows 95/98/ME,必须安装Microsoft的“Unicode兼容层”。

源代码

您会发现非常干净的源代码,具有整洁的错误处理和由经验丰富的程序员编写的大量注释。

您将获得高质量的库,并节省数周的编码时间。代码是可重用的,例如,您可以重用Internet类进行FTP/HTTP(S)下载或Blowfish类。

您可以研究LibExtract.h,了解托管回调如何传递给非托管C++代码(这并不容易,需要gcroot或GCHandle)。
所有代码均采用纯C++/托管C++编写,不使用MFC,以避免在您的应用程序运行的计算机上缺少DLL的问题。

不同的CAB文件格式

有两种完全不同的CAB文件类型:此项目支持的是“Microsoft CAB”文件(也称为“MS-CAB”)。内部打包格式可能是Microsoft的MSZIPLZX。几年后,InstallShield创建了“InstallShield CAB”文件。但它们与MS-CAB文件完全不兼容,尽管它们使用相同的扩展名!

如果您用十六进制编辑器打开MS-CAB文件,您会注意到前四个字节是“MSCF”(MicroSoft Cab File),而InstallShield-CAB文件的前三个字节是“ISc”。(InstallShield Cab)。您无法使用此项目打开或创建InstallShield CAB文件。能够管理InstallShield CAB文件的工具非常少;例如,您可以在我的主页上下载的WinPack工具。

压缩率

MS-CAB文件具有非常好的压缩率,特别是如果您使用LZX压缩算法。为了测试这一点,我打包了一堆大约一百个文本文件。这是我的测试结果

打包格式 打包文件大小
CAB (LZX) 125 kB
CAB (MSZIP) 139 kB
TAR + GZ 142 kB
ARJ 174 kB
TAR + LZH 189 kB
RAR 197 kB
TAR + JAR 242 kB
ZIP 242 kB

MSZIP 与 LZX 对比

您可以在两种压缩算法之间选择

功能 MSZIP LZX
速度 更快 更慢
压缩率 较低 较高
单独压缩文件
支持部分URL提取

MSZIP单独压缩每个文件,然后将压缩数据存储到CAB文件中。
LZX的工作方式完全不同:它首先从所有文件中创建一个霍夫曼树,然后将整个树存储到CAB文件中。
这种差异在您多次压缩同一个文件时会很明显
用MSZIP压缩3个相同(或几乎相同)的文件,CAB大小是压缩一个文件的300%。
用LZX压缩3个相同(或几乎相同)的文件,CAB大小大约是原始大小的110%。

智能安装程序

Microsoft创建CAB文件的目的是用于安装

  • 它们用于Internet Explorer 6的安装。
  • 您可以在Windows 95/98/ME安装CD上看到很多CAB文件。
  • Windows 2000/XP安装CD上所有以_结尾的文件,如“Kernel32.dl_”,都是文件扩展名错误的文件。

许多安装程序很笨拙。如果您启动一个不智能的安装程序(如Nero 6的安装程序),您会看到它首先将所有文件从打包的EXE安装程序中提取到一个临时目录。

  1. 这很慢,用户必须等待第一个对话框打开。
  2. 这会浪费磁盘空间,如果用户C盘上的可用空间不足,他们将收到“磁盘空间不足”的错误。
  3. 如果安装中断,可能会导致临时文件留在磁盘上。

与此Cabinet库不同,您可以构建一个智能安装程序

场景1。您只向客户提供两个文件:一个微小的EXE文件和一个巨大的CAB文件

将一个巨大的CAB文件放在本地服务器或CD或DVD上,让用户只启动一个微小的EXE安装程序文件。安装程序将立即启动,并仅从CAB文件中提取真正需要的文件。此Cabinet库显然可以提取整个CAB文件。但也可以直接从服务器/CD/DVD上的CAB文件中提取特定文件到硬盘。数据传输是压缩的,并且-如果您愿意-是加密的。

场景2。您只向客户提供一个巨大的EXE文件

您还可以将CAB文件嵌入到Setup.exe中,并直接从嵌入式资源中提取特定文件,而无需创建临时文件。由于Windows通过文件映射直接从磁盘访问嵌入式资源,而不是将它们加载到内存中,因此即使是巨大的嵌入式CAB文件,内存使用也不会有问题。

场景3。您只提供一个微小的EXE和一个URL给您的客户

如果您选择URL提取,您只提供一个微小的EXE文件,该文件将从互联网下载CAB文件。(FTP或HTTP(S)

如果您将其用于更新,您甚至可以配置Cabinet库,使其仅下载CAB档案中需要更新的文件。例如:您的公司销售一个由500个文件组成的ASP服务器。您将一个100兆字节的CAB文件放在您的更新服务器上,其中包含所有文件。假设客户想要更新到最新版本,只需要替换500个文件中的15个文件。Cabinet库将只从您的服务器下载2兆字节而不是100兆字节!数据传输是压缩的,并且可选地加密。

如果您需要安装程序/更新程序,请下载我的项目“一个智能的.NET多语言安装程序”。

C++项目

要将CAB支持添加到您的C++项目,请下载页面顶部的Cabinet项目(包含演示应用程序),并将整个“Cabinet”子文件夹复制到您的项目中。
此外,您必须手动将Static.cpp添加到解决方案资源管理器中,然后右键单击它,然后在“设置”->“预编译头”下选择“不使用预编译头”,否则您会收到C1010:在查找预编译头指令时遇到文件结尾。

.NET项目

第二个项目是为.NET开发人员准备的。我用托管C++围绕这个C++项目编写了一个包装器。结果编译成一个.NET DLL。您只需将.NET程序集CabLib.dll添加到您的.NET项目(C#Visual Basic .NET托管C++)的引用中,即可获得CAB支持。在顶部的第二个下载中,您将找到已编译并准备使用的CabLib.DLL。(包含演示应用程序)

Cabinet.dll

Microsoft的微型Cabinet.dll自Windows NT/98起就位于您的System(32)目录中,它提供了以下压缩API:

FciCreate FciAddFile FciFlushCabinet FciFlushFolder FciDestroy

 

 

以及解压缩API

FdiCreate FdiIsCabinet FdiCopy FdiDestroy

 

 

您可以在Microsoft Cabinet.dll Doku.doc文件中找到这些函数的详细描述,该文件位于两个项目中,而FCI.HFDI.H文件包含大量注释。

您可以通过在Defines.h文件中设置来消除对Cabinet.dll的外部依赖

#define STATIC_LINK_CABINET_DLL TRUE

Cabinet.Dll中的API使用了一系列回调,它们在CAB文件创建或解压缩时被调用。C++项目包装了这些回调,您可以覆盖每个回调函数来修改行为。

.NET项目提供了事件,您可以使用它们来处理.NET应用程序中的这些回调。

您可以使用这些回调/事件来过滤特定文件,或者您可以从流或内存中读取压缩数据,而不是从磁盘上的文件中读取。这使得库极其通用。(示例见下文)

压缩

文件压缩

以下示例将压缩到文件C:\Temp\Packed.cab
文件C:\Windows\Explorer.exe将被打包到CAB文件中的子文件夹FileManager
文件C:\Windows\Notepad.exe将被打包到CAB文件中的子文件夹TextManager

 

C++

Cabinet::CCompress i_Compress;
if (!i_Compress.CreateFCIContextW(L"C:\\Temp\\Packed.cab"))
    { Error handling... }
 
if (!i_Compress.AddFileW(L"C:\\Windows\\Explorer.exe", L"FileManager\\Explorer.exe", E_ComprMSZIP, 0))
    { Error handling... }
 
 
if (!i_Compress.AddFileW(L"C:\\Windows\\Notepad.exe",  L"TextManager\\Notepad.exe", E_ComprMSZIP, 0))
    { Error handling... }
 
if (!i_Compress.DestroyFCIContext())
    { Error handling... }

使用AddFile("...", "...", E_ComprNONE, 0)可以关闭压缩。指定的未压缩文件将被存储到cabinet中。这对于Pocket PC上的安装可能是必需的。

C#

ArrayList i_Files = new ArrayList();
i_Files.Add(new string[] { @"C:\Windows\Explorer.exe", @"FileManager\Explorer.exe" });
i_Files.Add(new string[] { @"C:\Windows\Notepad.exe",  @"TextManager\Notepad.exe"  });
 
CabLib.Compress i_Compress = new CabLib.Compress();
i_Compress.CompressFileList(i_Files, @"C:\Temp\Packed.cab", true, true, 0);

VisualBasic .NET

Dim i_Files As ArrayList = New ArrayList
i_Files.Add(New String() { "C:\Windows\Explorer.exe", "FileManager\Explorer.exe" })
i_Files.Add(New String() { "C:\Windows\Notepad.exe",  "TextManager\Notepad.exe"  })
 
Dim i_Compress As CabLib.Compress = New CabLib.Compress
i_Compress.CompressFileList(i_Files, "C:\Temp\Packed.cab", True, True, 0)

文件夹压缩

您还可以轻松地将文件夹“C:\Web”及其所有子文件夹中的所有HTM文件压缩到一个CAB文件中,该文件将反映硬盘上的文件夹结构。

C#

CabLib.Compress i_Compress = new CabLib.Compress();
i_Compress.SwitchCompression(eCompress.LZX);
i_Compress.CompressFolder(@"C:\Web", @"C:\Temp\Packed.cab", "*.htm", true, true, 0);

C++

Cabinet::CCompress i_Compress;
if (!i_Compress.CreateFCIContextW(L"C:\\Temp\\Packed.cab"))
    { Error handling... }
 
if (!i_Compress.AddFolderW(L"C:\\Web", L"*.htm", E_ComprLZX))
    { Error handling... }
 
if (!i_Compress.DestroyFCIContext())
    { Error handling... }

压缩分割

如果您想在大小有限的介质上分发数据,或者在网页上下载,您可以将CAB文件分割成多个部分,解压缩函数稍后会自动将它们重新组合。

以下示例将创建200 KB的CAB文件。
在这种情况下,文件名!必须!在结尾包含一个%d
允许的最小分割大小为20 KB。最大为2 GB。

C++

Cabinet::CCompress i_Compress;
if (!i_Compress.CreateFCIContextW(L"C:\\Temp\\Packed_%d.cab", TRUE, TRUE, 200000))
    { Error handling... }
 
etc..

C#

i_Compress.CompressFileList(i_Files, @"C:\Temp\Packed_%d.cab", true, true, 200000);
or
i_Compress.CompressFolder(@"C:\Web", @"C:\Temp\Packed_%d.cab", "*.htm", true, true, 200000);

设置压缩临时目录

压缩过程中,Cabinet.DLL将创建一些临时文件,然后自动删除。

默认情况下,它使用Windows指定的TEMP目录。如果您想压缩大文件而C盘空间不足,则应在另一个驱动器上指定一个TEMP目录。可以使用与CAB文件的输出文件夹相同的目录作为TEMP目录。

C++和C#

i_Compress.SetTempDirectory("E:\\Temp");

加密

您可以使用密钥加密CAB文件。C++代码使用Blowfish算法以8字节块“即时”加密/解密CAB数据。Blowfish是一种非常快速、对称、免版税的算法。

您可以将任何长度最多为72字节的二进制数据用作加密密钥。如果密钥长度超过72字节,则忽略其余字节。如果密钥长度小于72字节,则会重用某些字节。

可以使用明文密码直接进行Blowfish加密,但不推荐。(参见:KDF)相反,您应该从明文密码派生一个二进制哈希。

.NET代码通过SHA 512哈希来实现此目的,该哈希始终具有64字节的长度。

在.NET项目中,您可以设置任意长度的明文密码(字符串),首先从中派生一个64字节的SHA哈希,然后使用此哈希作为Blowfish加密CAB数据的密钥。

您还可以直接将自己的二进制数据设置为Blowfish的密钥。

C#

i_Compress.SetEncryptionKey(String);   // SHA 512 + Blowfish

i_Compress.SetEncryptionKey(Byte[72]); // only Blowfish

C++

i_Compress.SetEncryptionKey(void* p_Key, DWORD u32_KeyLength); // only Blowfish

更多压缩函数

通常您不需要以下C++函数

使用i_Compress.SwitchCompression(eCompress)可以关闭压缩,然后所有后续文件都将以未压缩形式存储到cabinet中。这对于Pocket PC上的安装可能是必需的。或者,您可以选择LZX和MSZIP算法。

使用i_Compress.AbortOperation()可以中止耗时的压缩。显然,这必须从另一个线程调用。

使用i_Compress.FlushFolder()可以强制完成当前文件夹。

使用i_Compress.FlushCabinet()强制关闭当前CAB文件,并将要添加的任何其他文件写入分割序列中的下一个CAB文件。

有关更多详细信息,请参阅文件Microsoft Cabinet.dll Doku.doc以及FCI.H文件中的大量注释。

使用CabLib.Compress.EnumFiles()(仅限C#),您可以创建文件列表。您可以为要压缩的每个文件扩展名调用此函数一次,然后将ArrayList传递给CompressFileList()

使用CabLib.Compress.Sprintf()(仅限C#),您可以使用旧的swprintf功能格式化.NET字符串,该功能在C#中尚不可用。该命令允许格式字符串如“File_%02d.cab”以及任意数量的任何类型的参数。

压缩回调/事件

CCompress.OnFilePlaced() (C++ 回调)
CabLib.Compress.evFilePlaced (.NET 事件)
每次成功将文件放置到cabinet中时都会调用此函数。

CCompress.OnUpdateStatus() (C++ 回调)
CabLib.Compress.evUpdateStatus (.NET 事件)

这可以用来更新您的GUI,以显示耗时压缩的进度。

注意

建议从另一个线程开始压缩,以避免GUI死锁,并能够调用AbortOperation()并显示进度。

在C#中,您必须在事件处理程序例程中调用Control.BeginInvoke()以异步访问GUI元素,否则您会遇到麻烦!

有关详细信息,请参阅文件Microsoft Cabinet.dll Doku.doc以及FCI.H文件中的大量注释。

扩展/修改

如果您想要不同的压缩行为,请不要修改现有的压缩类CCompress。相反,派生一个新类自CCompress并覆盖您想更改的函数。

Unicode

Unicode文件名在CAB存档中使用UTF8编码进行压缩。

解压缩CAB文件时,原始Unicode文件名将被恢复。这比MBCS(多字节)的优点是编码独立于任何代码页,避免了很多问题。

如果您的应用程序在Windows 95/98/ME上运行,则必须安装Microsoft的Unicode兼容层。

要使用Unicode功能,需要用UNICODE编译器开关(#ifdef UNICODE)编译C++项目。即使编译为MBCS,它也能正确工作,因为代码显式使用了宽版本的函数,如CreateFileW()

UTF8是CAB文件名的标准编码。您可以在Windows资源管理器中以UTF8文件名打开CAB文件。

如果CAB文件在Windows资源管理器中无法打开,请执行ZIP文件中的“Open_CAB_with_Explorer.reg”文件!

注意
并非所有程序都能正确显示CAB文件中的UTF8文件名。在Windows资源管理器WinZip中它们显示正确。但在WinRar 3.80WinAce 2.69TotalCommander 7.0中,Unicode文件名显示为损坏。这些程序没有UTF8支持。请联系他们的支持并要求他们添加!

禁用UTF8编码

如果您知道您的用户将使用不支持UTF8文件名的解包器(例如WinRar),则禁用UTF8编码是一种解决方法。在这种情况下,可以使用像áéíóúñöäüß这样的字符命名文件,这些字符通常是UTF8编码的,但在WinRar、WinAce等中显示为损坏。

无论您是否禁用UTF8编码:当解压缩CAB文件时,文件名将始终正确恢复,因为CAB文件中设置了一个看不见的标志,该标志表示文件名是UTF8编码还是不是。

CABCompressExtract/UnicodeTable.gif

压缩时可以禁用UTF8编码

 

i_Compress.CompressFileList(i_Files, s_CabFile, true, false);

如果您想将带有俄语/希腊语/中文文件名的文件Test.exe压缩到CAB文件中,则将始终需要UTF8编码。

如果您禁用了UTF8编码,以下操作将抛出异常

 

i_Files.Add(new string[] { "C:\\Test\\Test.exe",
                           "\u0416\u0435\u043B\u0435\u0437\u043D\u043E.exe" });

另一方面,即使禁用了UTF8编码,如果您将Unicode文件存储在ANSI名称下,您仍然可以压缩磁盘上的Unicode文件到CAB文件中。

i_Files.Add(new string[] { "C:\\Test\\\u0416\u0435\u043B\u0435\u0437\u043D\u043E.exe",
                           "Test.exe" });

在满足以下所有条件时才应禁用UTF8编码

  1. 您知道您的用户将使用不支持UTF8文件名的解包器(例如WinRar)。
  2. 您已向解包软件的支持人员发送电子邮件,恳请他们添加UTF8支持,以便将来可以删除此解决方法。
  3. 您确信要存储在CAB文件中的所有文件名都不使用ASCII码0xFF以上的字符。

UTC 时间

使用CreateFCIContext()中的参数b_UtcTime,您可以决定在压缩期间CAB档案是存储文件时间为UTC时间还是本地时间。提取时,库会自动使用正确的时间,因为CAB文件中设置了一个看不见的标志,该标志表示时间是UTC还是不是。

Windows文件系统始终以UTC时间存储所有文件时间!您在资源管理器中看到的文件时间取决于您的时区和夏令时。(在资源管理器中打开一个文件夹,记住文件时间,更改控制面板中的时区,您会发现磁盘上的所有文件都显示另一个时间了!!)

b_UtcTime = false:

  • 压缩:从硬盘读取UTC文件时间,转换为本地时间并存储在CAB文件中。
  • 提取:从CAB文件中读取本地时间,转换为UTC时间并存储在硬盘上。

b_UtcTime = true:

  • 压缩:从硬盘读取UTC文件时间,并以不变的形式存储在CAB文件中。
  • 提取:从CAB文件中读取UTC时间,并以不变的形式存储在硬盘上。

建议使用UTC时间进行压缩,这样在更改PC的时区夏令时更改后,CAB存档中的文件和磁盘上的文件将仍然具有相同的时间。

另一方面,如果您使用本地时间进行压缩/提取,冬天提取的CAB文件与夏天提取的CAB文件相比会有一个小时的时间偏移。

多线程

C++和.NET项目都支持用于压缩和提取的多线程。
Microsoft Cabinet.dll文档说明
“FCI和FDI支持多个同时上下文,因此可以在同一应用程序中同时创建或提取多个cabinet。如果应用程序是多线程的,也可以在每个线程中运行不同的上下文;但是,应用程序不允许在多个线程中同时使用相同的上下文(例如,不能从两个不同的线程调用FCIAddFile,使用相同的FCI上下文)。”

所以您永远不能从两个线程访问一个类实例。(除了AbortOperation()
这意味着,如果您想同时从多个线程压缩或解压缩多个CAB文件,每个线程必须使用自己的CExtract/CCompress实例。

在C++中还有一个额外的注意事项
在使用同一线程中的新CExtract/Resource/Url实例之前,必须先执行其析构函数,或者您必须手动调用CleanUp()

C++

Cabinet::CExtract i_Extract;
  ...work with i_Extract
  i_Extract.CleanUp();
  
  Cabinet::CExtractResource i_ExtractRes;
  ...work with i_ExtractRes
  i_ExtractRes.CleanUp();

文件提取

提取过程中不会创建临时文件。

以下示例将文件C:\Temp\Packed.cab提取到文件夹E:\ExtractFolder。如果CAB文件包含子文件夹,将自动创建所需的子文件夹。

C++

Cabinet::CExtract i_Extract;
if (!i_Extract.CreateFDIContext()) 
    { Error Handling ... }
if (!i_Extract.ExtractFileW(L"C:\\Temp\\Packed.cab", L"E:\\ExtractFolder"))
    { Error Handling ... }

C#

CabLib.Extract i_Extract = new CabLib.Extract();
i_Extract.ExtractFile(@"C:\Temp\Packed.cab", @"E:\ExtractFolder");

.NET库会自动检测扩展名为.LNK的文件并解析快捷方式(到CAB文件),而扩展名为.URL的文件将重定向到URL提取。

Win32资源提取

以下示例提取存储在DLL或EXE文件的Win32资源中的Cabinet文件。您可以直接从内存中的CAB文件提取文件

在将CAB文件添加到项目资源时,需要遵守一些规则。

在C++项目的Cabinet.rc文件和.NET项目的CabLib.rc文件中,您会找到这一行

ID_CAB_TEST             CABFILE                 "Res\\Test.cab"

而在Resource.h文件中,您会找到这一行

#define ID_CAB_TEST                     101

重要提示
如果您在Resource.h中定义了ID_CAB_TEST,则资源将存储在
ResourceName = 101         (整数)
ResourceType = "CABFILE" (字符串)

如果您Resource.h中定义ID_CAB_TEST,则资源将存储在
ResourceName = "ID_CAB_TEST" (字符串)
ResourceType = "CABFILE"         (字符串)

 

如果您在CabLib.rc中将“CABFILE”替换为预定义值之一
例如“RCDATA”,则适用以下规则

如果您在Resource.h中定义了ID_CAB_TEST,则资源将存储在
ResourceName = 101   (整数)
ResourceType =   10   (RT_RCDATA的整数值,参见WinUser.h)

如果您Resource.h中定义ID_CAB_TEST,则资源将存储在
ResourceName = "ID_CAB_TEST" (字符串)
ResourceType =  10                 (RT_RCDATA的整数值,参见WinUser.h)

 

要提取嵌入式资源Test.cab(我已将其添加到两个项目中),请写入

C++

Cabinet::CExtractResource i_Extract;
if (!i_Extract.CreateFDIContext()) 
    { Error Handling ... }

if (!i_Extract.ExtractResourceW(L"Cabinet.exe", ID_CAB_TEST, L"CABFILE",
                                L"C:\\ExtractFolder"))
    { Error Handling ... }

C#

CabLib.Extract i_Extract = new CabLib.Extract();
i_Extract.ExtractResource("CabLib.dll", 101, "CABFILE", @"C:\ExtractFolder");

第一个参数指定文件名(不含路径),从中提取Win32 CAB资源。如果资源在创建进程的EXE内部,您可以将其设置为= 0(null)。

可以嵌入分割的CAB文件
它们可以作为资源编译,资源名称为字符串,如“Part_01.cab”、“Part_02.cab”、“Part_03.cab”,或具有连续ID,如101、102、103。
注意:如果您使用字符串,资源名称必须与您压缩的文件名相同!

您可以使用此功能从当前加载到进程中的任何DLL或从应用程序EXE本身提取CAB文件。
要探索已编译文件的资源,请下载ResourceHacker工具。

大多数Windows更新补丁都包含一个CAB文件。

.NET资源提取/流提取

.NET以完全不同的方式存储资源,因此您在ResourceHacker工具中看不到它们。
在资源的属性下,您必须将构建操作设置为“嵌入式资源”。

要提取名为MyProject的项目的子文件夹Resources中名为Test.cab的文件,请写入

C#

System.Reflection.Assembly i_Ass  = System.Reflection.Assembly.GetExecutingAssembly();
System.IO.Stream i_Strm = i_Ass.GetManifestResourceStream("MyProject.Resources.Test.cab");
 
CabLib.Extract i_Extract = new CabLib.Extract();
i_Extract.ExtractStream(i_Strm, @"E:\ExtractFolder");

URL提取

假设您想构建一个更新程序,将您的软件包更新到客户端计算机上的最新版本。如果您的软件包包含500个文件,其中只有15个文件在最新版本中发生了更改,您可以提供一个包含这15个文件的更新补丁。

一个更智能的解决方案是构建一个更新程序,该程序仅从更新服务器上的巨大CAB存档中提取需要更新的文件。此CAB存档包含您的整个软件包的最新版本,无论您的客户端计算机上当前安装的是哪个版本,更新程序都只会下载过时的文件。

传输的数据将是压缩数据,并且可选地是加密数据。(参见CAB加密)

显然,您需要将一个文件列表与所有文件的MD5一起打包到CAB存档中,以便更新程序提前知道需要下载哪些文件,以及哪些文件是最新的。

一个使用此类URL提取(及更多功能)的完整安装程序项目可以在这里找到:“一个智能的.NET多语言安装程序”。

档案的前1%左右是一个索引,其中包含文件名和指向压缩数据的指针。

Cabinet库读取此索引,并为每个文件调用回调函数OnBeforeCopyFile。(见下文)

如果您在此回调中返回FALSE,则文件既不会被下载也不会被提取。

要能够仅部分下载服务器上的文件,您有两种选择

  1. 一个支持恢复中断下载的FTP服务器(服务器必须支持FTP命令“REST”(Restart))。
    URL:ftp://user:password@ftp.server.com:Port/Updates/Setup_1.35.cab
  2. 一个支持恢复中断下载的HTTP(S)服务器(服务器必须支持HTTP标头“Range:”)。
    URL:http(s)://www.server.com:Port/Updates/Setup_1.35.cab
    HTTP URL如果需要,还可以包含用户名和密码。

所有实际服务器(IIS、Apache等)都支持这些命令。

重要提示:
部分URL提取需要MSZIP压缩的CAB文件。由于LZX存储数据的方式,它不能用于部分URL提取!

Microsoft的Cabinet.dll在FDIRead回调中请求8字节到32000字节的块大小。从服务器请求如此小的块是没有意义的。此外,Cabinet.Dll并非完全按顺序访问CAB数据。为此,Cabinet库在Cabinet.dll和Internet之间使用CCache类,以确保 no data block must be read twice and to improve the performance.

一个重要因素是缓存从服务器读取的块的大小。

小的块大小会导致性能不佳,因为每个块都会为服务器打开一个新的数据连接。太大的块大小会下载比实际需要更多的数据,尤其是在只提取大型档案中的特定文件时。

我建议在Trace.hpp文件中启用跟踪,并玩弄块大小。在跟踪中,您将看到下载速度(KB/s)。

如果您不想使用仅提取CAB文件部分的功能,强烈建议您将整个CAB档案下载到磁盘上的临时文件,然后进行提取。Cabinet库将自动为您完成此操作,并在提取后删除临时文件。这可以实现最大的下载速度,因为服务器不断传输数据流。

通过将块大小=0来启用将整个CAB文件下载到磁盘。

 

块大小
URL
注意
块大小= 50 kB 下载速度慢,但不会下载不必要的数据。
用于部分更新,不建议用于完整安装。
HTTP(S)服务器必须
支持“Range:”标头

FTP服务器必须
支持“REST”命令
块大小= 1 MB 下载速度更快,但下载了更多不必要的数据。
用于部分更新,不建议用于完整安装。
块大小= 0 最高下载速度,下载整个CAB,然后提取。
用于完整安装,不建议用于部分更新。
无特殊
服务器要求

 

 

您不应使用大于2 MB的块大小,因为这样做不会带来任何优势。请参阅我ExtractUrl.hpp文件中的注释!
如果块大小为零,下载仅使用内存中的小缓冲区将数据复制到磁盘。

如果您未在URL中指定用户名和密码,FTP将使用匿名登录

CExtractUrl类使用Wininet.dll中的Internet功能从服务器下载数据。需要Internet Explorer 5.0或更高版本。

C++

Cabinet::CExtractUrl i_Extract;
if (!i_Extract.CreateFDIContext()) 
    { Error Handling ... }
 
if (!i_Extract.ExtractUrlW(URL, Blocksize, DownloadFile, ExtractFolder))
    { Error Handling ... }
 
i_Extract.CleanUp(); // see below

C#

CabLib.Extract i_Extract = new CabLib.Extract();
i_Extract.ExtractUrl(URL, Blocksize, DownloadFile, ExtractFolder);
i_Extract.CleanUp(); // see below

使用此功能有几种选项

将整个CAB文件下载到“C:\Installer.cab”,然后提取
块大小== 0 DownloadFile = "C:\Installer.cab" ExtractFolder = "C:\Extracted"
将整个CAB文件下载到临时文件,提取,然后删除
块大小== 0 DownloadFile = "" ExtractFolder = "C:\Extracted"
从FTP/HTTP(S)下载任何文件到磁盘而不进行CAB提取
块大小== 0 DownloadFile = "C:\Metallica.mp3" ExtractFolder = ""
将CAB文件块下载到内存并提取它们
块大小> 0 DownloadFile = "" ExtractFolder = "C:\Extracted"

您可以多次从服务器上的同一个CAB文件提取,这样就可以重用Internet缓存的内容。下载的临时文件也可以这样重用。

i_Extract.SetSingleFile(FileName1);
i_Extract.ExtractUrl(Url, Blocksize, DownloadFile, ExtractFolder);
i_Extract.SetSingleFile(FileName2);
i_Extract.ExtractMoreUrl(ExtractFolder);
i_Extract.CleanUp(); // see below

更多Internet函数

使用i_Extract.CleanUp()可以释放Internet缓存的内存并关闭下载的文件。完成后请调用此函数。

重要提示:
CleanUp()不会自动调用,以便允许重用部分下载到内存或完整下载到临时文件以供稍后从同一CAB文件提取。所以您在最后一次URL提取后必须手动调用CleanUp(),因为库无法知道您何时完成。

使用i_Extract.SetProxy()可以为HTTP、HTTPS、FTP指定CERN、TIS或SOCKS代理。

字符串的格式必须是“http=http://Proxy1.com:8000 https=https://Proxy2.com:443”。如果您传递一个空字符串或从不调用此函数,将使用Internet Explorer的默认设置。(存储在注册表中)

使用i_Extract.SetPassiveFtpMode()可以切换到主动或被动FTP模式。如果您从不调用此函数,则将使用被动模式。

使用i_Extract.SetHttpHeaders()可以指定发送到服务器的其他HTTP标头。

标头由“Name: Value”组成。多个标头可以用管道字符分隔。例如:“Referer: http://www.test.com|Accept-Language:en”。管道字符前后不能有空格!

使用i_Extract.InternetGetProgress()可以在进度条中显示下载进度。(这不是回调!)提取必须从另一个线程开始,并且在GUI线程中您必须设置一个计时器(例如500毫秒)来轮询此函数中的进度。对于部分更新,进度条意义不大。

(如果您指定了块大小> 0,进度条将在每个下载的块重新开始为零。)

注意:并非所有HTTP服务器都返回“CONTENT-LENGTH”(例如,AOL服务器),在这种情况下,进度条将不起作用。

代码页

当提取此库压缩的CAB文件时,您不必担心代码页的噩梦。压缩将文件名编码为UTF-8,并设置_A_NAME_IS_UTF标志,该标志存储在CAB文件中。此标志指示提取文件名是UTF-8编码的。(参见Unicode章节)

但是,如果您想提取在Windows 98上使用希腊代码页或OEM(=DOS)代码页压缩的旧CAB文件,并且使用了不将文件名UTF编码的压缩器,该怎么办?

在这种情况下(!!并且 仅在此情况下!!),您可以定义代码页

i_Extract.SetCodepage(850); // OEM Codepage

注意 

  1. 非UTF文件名的默认转换是ANSI代码页(1252)。
  2. 如果CAB文件中的_A_NAME_IS_UTF标志已设置,则您的代码页设置将被忽略!

特殊功能

更多提取函数

使用i_Extract.AbortOperation()可以中止耗时的提取。显然,这必须从另一个线程调用。

使用i_Extract.IsCabinet()可以检查指定的CAB文件是否有效或已损坏。此外,此函数返回一个包含CAB文件信息的结构。这些信息是从CAB文件头读取的。例如,您可以获得CAB文件中文件的总数,如果此CAB是分割存档的一部分,等等。

使用i_Extract.SetSingleFile()可以仅从cabinet中提取指定的文件。该文件必须位于CAB的根文件夹中。(见下文)

使用CabLib.Extract.ResolveShortcut()可以获取*.LNK或*.URL文件的内容。

解密

与加密类似,您可以解密存档。(参见上面的加密)

如何仅从CAB文件/资源/流中提取一个文件

C#

CabLib.Extract i_Extract = new CabLib.Extract();
i_Extract.SetSingleFile("FileList.xml");
i_Extract.ExtractFile(@"C:\Temp\Packed.cab", @"E:\ExtractFolder");

这将创建一个文件E:\ExtractFolder\FileList.xml

要提取的文件必须位于CAB存档的根文件夹中。

如果文件在存档中不存在,将不会发生任何事情。(无错误)

如果提取单个文件,则不会触发事件evBeforeCopyFile。(见下文)

C++

CExtract::kCallbacks k_Callbacks;
k_Callbacks.f_OnBeforeCopyFile = &CMain::OnBeforeCopySingleFile;
i_Extract.SetCallbacks(&k_Callbacks);
 
CStrW s_SingleFile = L"FileList.xml";
i_Extract.ExtractFileW(L"C:\\Temp\\Packed.cab", "E:\\ExtractFolder", (void*)&s_SingleFile);
 
BOOL CMain::OnBeforeCopySingleFile(CExtract::kCabinetFileInfo *pk_Info, void* p_Param)
{
    CStrW* ps_SingleFile = (CStrW*)p_Param;
    return (*ps_SingleFile == pk_Info->u16_File);
}

提取回调/事件

CExtract.OnBeforeCopyFile() (C++ 回调)
CabLib.Extract.evBeforeCopyFile (.NET 事件)

在Cabinet.dll将提取的文件复制到磁盘之前调用此函数。您将获得有关要提取的文件的详细信息。如果您不希望将此文件复制到磁盘,可以在此处返回FALSE,该文件将被跳过。(示例见下文)
此回调可用于在GUI中显示进度信息,如果您提取小文件(< 20 MB)。

CExtract.OnAfterCopyFile() (C++ 回调)
CabLib.Extract.evAfterCopyFile (.NET 事件)

在Cabinet.dll将新文件放到磁盘之后调用此函数。
此回调可用于在GUI中显示进度信息,如果您提取小文件(< 20 MB)。
如果您启用了内存提取,则不会将文件写入磁盘,而是此回调在缓冲区/字节数组中接收文件的全部内容。

CExtract.OnProgessInfo()   (C++ 回调)
CabLib.Extract.evProgressInfo (.NET 事件)如果提取大文件(> 50 MB)并想在GUI中显示进度,则必须使用此回调。
在写入大文件时,每200毫秒调用一次此回调。示例

  1. OnBeforeCopyFile()
  2. OnProgressInfo()--> 27,5 %
  3. OnProgressInfo()--> 52,9 %
  4. OnProgressInfo()--> 81,3 %
  5. OnAfterCopyFile()

CExtract.OnCabinetInfo() (C++ 回调)
CabLib.Extract.evCabinetInfo (.NET 事件)

每次打开一个 cabinet 时,将恰好调用此函数一次。它传递有关CAB文件的信息。

CExtract.OnNextCabinet() (C++ 回调)
CabLib.Extract.evNextCabinet (.NET 事件)

当需要打开分割CAB序列中的下一个cabinet文件时,将调用此函数。
在这里您可以显示消息,如“请插入磁盘2!”

注意

建议从另一个线程开始提取,以避免GUI死锁,并能够调用AbortOperation()并显示进度。

在C#中,您必须在事件处理程序例程中调用Control.BeginInvoke()以异步访问GUI元素,否则您会遇到麻烦!

有关详细信息,请参阅文件Microsoft Cabinet.dll Doku.doc以及FDI.H文件中的大量注释。

操作提取过程

使用回调OnBeforeCopyFile(),您可以精确控制要从CAB文件中提取的内容。回调/事件传递一个结构kCabinetFileInfo,其中包含有关要提取文件的详细信息:文件名、子文件夹、完整路径、文件大小、文件日期/时间和文件属性。

使用此信息,您可以决定是否要提取该文件,如果不提取则返回false

在C#中,您必须先附加一个事件处理程序

C#

CabLib.Extract.delBeforeCopyFile i_Delegate = 
    new CabLib.Extract.delBeforeCopyFile(OnBeforeCopyFile);

i_Extract.evBeforeCopyFile += i_Delegate;
 
i_Extract.ExtractResource("CabLib.dll", 101, "CABFILE", @"E:\ExtractFoder");
 
i_Extract.evBeforeCopyFile -= i_Delegate;

C++

CExtract::kCallbacks k_Callbacks;
k_Callbacks.f_OnBeforeCopyFile = &CMain::OnBeforeCopyFile;
 
i_Extract.SetCallbacks(&k_Callbacks);

如何仅提取具有特定文件扩展名的文件

如果您只想从CAB中提取扩展名为“.DLL”的文件(包括所有子文件夹),可以编写

C++

BOOL OnBeforeCopyFile(kCabinetFileInfo &k_Info, void* p_Param)
{ 
    int Len = wcslen(k_Info.u16_File);   // length of filename
    return (wcsicmp(k_Info.u16_File +Len -4, L".DLL") == 0);
}

C#

private bool OnBeforeCopyFile(CabLib.Extract.kCabinetFileInfo k_Info)
{
    return k_Info.s_File.ToUpper().EndsWith(".DLL");
}

如何仅提取CAB中的特定子文件夹内的文件

如果您想从名为“Setup\”的CAB中提取一个文件夹及其所有子文件夹,请编写

C++

BOOL OnBeforeCopyFile(kCabinetFileInfo &k_Info, void* p_Param)
{ 
    return (wcsnicmp(k_Info.u16_SubFolder, L"Setup\\", 6) == 0);
}

C#

private bool OnBeforeCopyFile(CabLib.Extract.kCabinetFileInfo k_Info)
{
    return k_Info.s_SubFolder.ToUpper().StartsWith(@"SETUP\");
}

如何仅提取较新的文件

如果您想更新现有文件,并且只想替换那些日期早于CAB中文件的文件,您可以编写

C++

BOOL OnBeforeCopyFile(kCabinetFileInfo &k_Info, void* p_Param)
{
    // try to open the file on disk
    HANDLE h_File = CreateFileW(k_Info.u16_FullPath, GENERIC_READ,
                                FILE_SHARE_READ, 0, OPEN_EXISTING, 
                                FILE_ATTRIBUTE_NORMAL, 0);
    
    // The file does not yet exist --> copy it!
 
    if (h_File == INVALID_HANDLE_VALUE)
        return TRUE;
    
    FILETIME k_FileTime, k_LocalTime;
    BOOL b_OK = GetFileTime(h_File, 0, 0, &k_FileTime);
    
    CloseHandle(h_File);
    
    if (!b_OK)
        return TRUE;
    
    // Last write time UTC --> Local time
    FileTimeToLocalFileTime(&k_FileTime,    &k_LocalTime);
    return (CompareFileTime(&k_Info.k_Time, &k_LocalTime) > 0);
}

C#

private bool OnBeforeCopyFile(CabLib.Extract.kCabinetFileInfo k_Info)
{
    if (!System.IO.File.Exists(k_Info.s_FullPath)) 
        return true;
    
    // retrieve local file time
    System.DateTime k_FileTime = System.IO.File.GetLastWriteTime(k_Info.s_FullPath);
    return (k_Info.k_Time.CompareTo(k_FileTime) > 0);
}

如何直接将文件提取到内存

例如,如果您有一个XML文件列表打包在CAB文件中,并且想将此文件列表加载到XML读取器中,那么首先将文件列表提取到磁盘上的临时文件,然后将其读入XML读取器,然后删除临时文件是没有意义的。

相反,您可以将目标目录设置为“MEMORY”直接将此文件提取到内存。

如果您从内存或流中提取到内存,您将进行一个内存内存的提取。

使用内存提取,您可以提取加密数据,而无需将解密后的明文数据写入磁盘。

重要提示
显然,您也可以提取多个文件到内存,通过省略下面示例中的SetSingleFile(..)。但是,如果您事先不知道您提取到内存的文件是小文件,则必须在回调OnBeforeCopyFile()中检查它们的大小。

此回调在为提取分配内存之前告诉您文件的大小。如果文件大于10兆字节,您不应将其提取到内存,并在OnBeforeCopyFile()中返回false以跳过此文件。如果您提取大文件到内存,内存有限的计算机将变得非常慢!

C#

CabLib.Extract i_Extract = new CabLib.Extract();
i_Extract.SetSingleFile("FileList.xml"); // see above
i_Extract.evAfterCopyFile += new CabLib.Extract.delAfterCopyFile(OnAfterCopyFile);
 
i_Extract.ExtractFile(@"C:\Temp\Packed.cab", "MEMORY");
 
private void OnAfterCopyFile(string s_File, Byte[] u8_ExtractMem)
{
    string s_Content = Encoding.UNICODE.GetString(u8_ExtractMem);
    
    // Cut the first character which identifies the Unicode encoding
    if (s_Content.Length > 1 && s_Content[0] == 0xFEFF)
        s_Content = s_Content.Substring(1);
                
    XmlDocument i_Doc = new XmlDocument();
    i_Doc.LoadXml(s_Content);
    .....
}

C++

Cabinet::CExtract i_Extract;
Cabinet::CExtract::kCallbacks k_Callbacks;
k_Callbacks.f_OnAfterCopyFile = &CMain::OnAfterCopyFile;
i_Extract.SetCallbacks(&k_Callbacks);
 
if (!i_Extract.CreateFDIContext())
   { Error Handling ... }
if (!i_Extract.ExtractFileW(L"C:\\Temp\\Packed.cab", L"MEMORY"))
   { Error Handling ... }
......
 
void CMain::OnAfterCopyFile(WCHAR* u16_File, Cabinet::CMemory* pi_ExtractMem,
     void* p_Param)
{
     if (wcsicmp(u16_File, L"MEMORY\\FileList.xml") == 0)
     {
          int  s32_Length;
          BYTE* u8_Data = pi_ExtractMem->GetData(&s32_Length);
          ParseXmlFileList(u8_Data); 
     }
}

如何获取CAB中的文件数

此信息存储在CAB头中。通过调用IsCabinet(),您可以获得有关CAB文件的许多信息,该函数会填充给定的结构。

C++

FDICABINETINFO k_Info;
BOOL b_ValidFile  = i_Extract.IsCabinetW(u16_File, &k_Info);
int s32_FileCount = k_Info.cFiles;

C#

CabLib.Extract.k_HeaderInfo k_Info;
bool b_ValidFile  = i_Extract.IsFileCabinet(s_File, out k_Info);
int s32_FileCount = k_Info.u16_Files;

如何获取CAB中所有文件的列表

启动一个正常的提取过程,并在OnBeforeCopyFile()中返回false。这样您就可以得到所有文件,但 nothing will be written to disk.
如果您需要更多信息,如大小、日期、属性,请将整个k_Info存储到ArrayList中。

C#

ArrayList mi_FileList = new ArrayList();
  
  private bool OnBeforeCopyFile(CabLib.Extract.kCabinetFileInfo k_Info)
  {
    mi_FileList.Add(k_Info.s_RelPath);
    return false;
    }

提取类层次结构

此图演示了两个项目中使用的C++类

如果您想要不同的行为,请不要修改现有类。而是派生一个新类自现有类并覆盖您想更改的函数。

CExtract包含从磁盘提取“真实”CAB文件的函数。

CExtract包含以下回调,这些回调从Cabinet.dll调用

  • Open() 用于打开文件
  • Read() 用于从文件读取
  • Write() 用于写入文件
  • Seek() 用于设置文件指针或询问其位置
  • Close() 用于关闭文件

重要提示:这些回调由Cabinet.dll调用,用于读取CAB文件以及将所有提取的文件写入磁盘。

CExtractMemory是一个类,它重写了文件访问函数,并用从内存而不是磁盘读取CAB数据的函数替换它们。CExtractMemory本身无法实例化。必须从它派生其他类。它提供了以下附加回调

  • OpenMem() 用于打开表示CAB文件的内存
  • ReadMem() 用于从CAB文件的内存中读取
  • SeekMem() 用于设置内存指针或询问其位置
  • CloseMem() 用于释放保存CAB文件的内存

重要提示:这些回调仅在Cabinet.dll想要读取CAB文件时调用。

CExtractResource派生自CExtractMemory,用于从Win32资源读取数据。CExtractStream派生自CExtractMemory,用于从.NET流读取数据。CExtractUrl派生自CExtractMemory,用于从Internet读取数据。

您可以轻松地派生自己的类,例如用于从管道或任何您喜欢的数据流中读取数据。数据流必须能够进行查找(随机访问)。

调试

如果您想使用像SysInternalsDebugView这样的工具来调试整个压缩/提取过程,您可以修改Trace.hpp文件。

#define _TraceCompress (_DEBUG && TRUE) // CAB compression
and / or
#define _TraceExtract  (_DEBUG && TRUE) // CAB extraction
and / or
#define _TraceInternet (_DEBUG && TRUE) // communication with the server
and / or
#define _TraceCache    (_DEBUG && TRUE) // storage of downloaded blocks in cache

重要提示
要在DebugView中看到任何内容,您必须在DEBUG模式下编译CabLib,并使用CTRL + F5在Visual Studio中启动编译后的应用程序。

基于CabLib的安装程序/更新程序

请查看我的项目“一个智能的.NET多语言安装程序”,该项目可以从本地CAB文件、文件服务器上的CAB文件或FTP/HTTP(S)服务器上的CAB文件安装或更新软件软件包。

如果您在使CabLib运行方面遇到任何问题,请研究Installer项目的源代码,该项目演示了所有功能。

附注
您可以从我的主页下载免费的C++书籍(编译成HTML格式)。

© . All rights reserved.