Microsoft Cabinet 模板





5.00/5 (14投票s)
2004年7月24日
10分钟阅读

96074

4403
本文展示了一组围绕 Microsoft Cabinet 库创建的模板。使用这些模板,您可以提取 Cabinet 文件以及存储在模块资源部分的 Cabinet 文件。它可以轻松扩展以允许通过其他方式进行提取。
引言
您是否曾忙于为您的一个项目寻找一个好的压缩库?您是否曾考虑过,从 Windows 98 和 Windows NT 4.0 开始,Microsoft 免费提供了一个非常好的压缩库?事实上,如果您不介意重新分发 DLL,它甚至可以在更早的版本中使用。我指的是可以免费下载的 Microsoft Cabinet SDK。
Microsoft Cabinet SDK
在我撰写本文时,SDK 可以在此处下载。该 SDK 包含以下几项内容:
- 几个使用 Cabinet 文件的二进制文件。
- 用于使用该库的源代码和头文件。
- 如何使用源代码和头文件的示例。
- 关于以下方面的文档:
- 如何使用二进制文件
- 文件格式
- 如何使用 API
- 算法
本文只讨论 API。目前,我只创建了用于提取 Cabinet 文件的类,但我将来可能会扩展这些类以支持现有和新的 Cabinet 文件。
背景
首先,让我告诉您我为什么决定创建这些类。Microsoft 提供的 API 功能非常强大,但它是用 C 编写的,并且使用起来相当困难。它需要大量的重复性任务,这在使用 Cabinet 文件时非常常见。这促使我产生了创建包装器的想法,这些包装器实现了这些常见任务,这样我就不必一遍又一遍地编写相同的代码。已经有一些文章提供了出色的类来处理 Cabinet 文件,但不幸的是,它们不符合我的需求。我想要一个具有以下属性的包装器:
- 它必须是一个轻量级且快速的包装器
- 它必须将所有重复性任务从您手中解放出来
- 它必须易于使用
- 它必须易于扩展,以便它可以处理通过文件以外的其他方式(例如,通过资源或管道)进行的提取
到目前为止,我发现的所有实现都没有提供最后一个属性。毕竟,将 Cabinet 存储在磁盘上,然后提取它并再次删除文件,这是非常麻烦和低效的,而您可以在内存中完成相同的操作。使用这些模板,通过您想要的任何电子介质提取 Cabinet 将相对容易。
基本模板:CCabinetT
我选择使用模板是因为它们的效率,因为这样我不需要虚函数(这在静态函数上甚至不可能,而在模板上是可能的,如您在源代码中看到的)。通过模板而不是普通虚函数实现“虚”函数的另一个优点是编译时链接而不是运行时链接,这意味着编译器可以进行优化。然而,这不是一篇关于模板的文章,而是关于 Cabinet 文件的文章,所以我将尽量保持专注。
基本模板 CCabinetT
完成大部分工作,并封装了 Cabinet API。使用它相当容易。它不能直接实例化,因此您必须编写一个子类。有一个空子类 CCabinet
,但我不建议使用它,因为它唯一能做的事情是提取 Cabinet 中的所有文件(CCabinetT
的默认行为)。对于本文,我将定义一个名为 CCabinetTutorial
的新类。目前,我将像这样声明它
class CCabinetTutorial: public CCabinetT<CCabinetTutorial> { };
您会问,就这些吗?是的,就这些。这正是 CCabinet
的定义方式。CCabinetT
提供了从 Cabinet 文件中提取文件的所有功能。所以,让我们看看它应该如何使用。以下片段展示了如何将 tutorial.cab 文件中的所有文件提取到 Extract 目录
CCabinetTutorial cabTutorial; // Create the FDI context if (!cabTutorial.CreateFDIContext()) return; // Verify if it is a real cabinet if (!cabTutorial.IsCabinet("tutorial.cab")) return; // Extract the files cabTutorial.Copy("tutorial.cab", "Extract"); // Destroy the FDI context, this is also done in the // constructor, but it is never wrong to do this yourself. cabTutorial.DestroyFDIContext();
那些以前使用过 Cabinet API 的人会立即认出这里显示的四个函数。那些不熟悉的人可能会想 FDI 上下文到底是什么。让我试着向您解释一下。Cabinet API 由两部分组成:FCI 和 FDI。FCI (File Compression Interface) 负责创建和压缩 Cabinet 文件,而 FDI (File Decompression Interface) 负责解压缩。FCI 和 FDI 各自需要自己的上下文才能工作。由于这些模板只能解压缩 Cabinet 文件,我们只使用 FDI。如果您想了解更多关于 FCI 和 FDI 工作原理的信息,我建议您查阅 Cabinet SDK 的文档。
除了 FDI 上下文,这个小例子应该是不言自明的。它创建上下文,检查目标文件是否是有效的 Cabinet 文件,并提取到指定文件夹。之后,它清理我们使用的资源。但等等!如果我们不想提取 Cabinet 中的所有文件怎么办?很简单,我们只需重写一些函数。CCabinetT
允许子类重写以下函数(就像它们是虚函数一样)
OnCabinetInfo
OnCopyFile
OnCopyFileComplete
OnNextCabinet
Alloc
免费
打开
读取
Write
Close
Seek
我将从解释粗体标记的函数开始。当打开新的 Cabinet 文件时,会调用 OnCabinetInfo
函数。参数包含有关 Cabinet 内容的一些基本信息。当文件即将被复制时,会调用 OnCopyFile
,您可以通过返回值允许或不允许复制文件。当文件被提取时,会调用 OnCopyFileComplete
,当 Cabinet 的内容跨越多个 Cabinet 文件时,会调用 OnNextCabinet
。您必须确保下一个 Cabinet 文件在返回之前是可访问的,例如,通过向用户提供插入另一张磁盘的机会。因此,让我们重做 CCabinetToturial
以实现这些函数。
class CCabinetTutorial : public CCabinetT<CCabinetTutorial> { private: void OnCabinetInfo(CABINETINFO& ci, LPVOID) { printf("Cabinet Info\n" " next cabinet: %s\n" " next disk: %s\n" " cabinet path: %s\n" " cabinet set ID: %d\n" " cabinet # in set: %d\n\n", ci.szNextCabinet, ci.szNextDisk, ci.szPath, ci.uSetID, ci.uCabinet ); } bool OnCopyFile(CABINETFILEINFO& cfi, LPCSTR szPath, LPVOID) { printf("Extracting '%s' to '%s'...", cfi.szFile, szPath); // Return false to cancel the extraction return true; } void OnCopyFileComplete(LPCSTR, LPVOID) { printf("...DONE\n\n"); } void OnNextCabinet(CABINETINFO& ci, FDIERROR, LPVOID) { printf("\n\nPlease insert the disk containing '%s' before " "pressing a button to continue\n", ci.szNextCabinet); getc(stdin); } // Baseclass has to access private members friend class CCabinetT<CCABINETTUTORIAL>; };
就这么简单!有关这些可重写函数的更多信息,请参阅 Cabinet SDK 的文档。我现在将讨论接下来的两个可重写函数
OnCabinetInfo
OnCopyFile
OnCopyFileComplete
OnNextCabinet
Alloc
免费
打开
读取
Write
Close
Seek
您可能已经猜到,这两个函数提供了内存管理。默认实现只是调用标准的 C++ new
和 delete
运算符。您可以像这样重写它们
class CCabinetTutorial : public CCabinetT<CCabinetTutorial> { private: static void * Alloc(size_t size) { return malloc(size); } static void Free(void * memblock) { free(memblock); } // Baseclass has to access private members friend class CCabinetT<CCABINETTUTORIAL>; };
请注意,这两个函数是静态的。模板允许“虚拟”静态函数不是很美妙吗 :)?无论如何,这些函数是不言自明的,而且很无聊,所以让我们进入真正的重点!
扩展类:CCabinetExT
您可能想知道我是否忘记告诉您其他可重写函数。好吧,我没有,因为这个模板就是关于它们的。这些可重写函数的默认实现只是封装了同名的 CRT I/O 函数。您可以在 MSDN 中阅读有关它们的内容。如果您只想使用标准 CRT 以外的其他 I/O API,您可以重写 CCabinetT
类。但是,要最大限度地利用重写这些函数,您需要使用 CCabinetExT
。这个类提供了您将基于文件的介质更改为不同介质所需的一切。您想在管道中访问 Cabinet?没问题!只需从这个模板派生一个类并填补缺失的拼图。
它是如何工作的?通过重写其余的可重写函数。问题是 Cabinet 库期望这些函数处理文件,所以我必须找到一种方法来在文件和用户定义方法上使用这些函数。我通过以下方式实现了这一点。Open
的第一个参数是一个字符串。通常,这是一个包含文件名的字符串。但是,通过以特定标记作为扩展名开头,我可以将文件请求与用户定义请求分开。例如,我创建一个标记 '//Resources\\'。现在,每次我执行以标记 '//Resources\\' 开头的 Copy
请求时,它都会调用用户定义的函数,而不是文件 I/O 函数。为了实现这一点,模板提供了以下可重写函数
GetIdentifier
OpenEx
ReadEx
WriteEx
CloseEx
SeekEx
函数 GetIdentifier
只是返回一个指向包含标记的字符串的指针。为了使扩展正确工作,必须重写此函数。应该重写 '~Ex' 函数,以提供不同的 Cabinet 处理实现。这些函数应该与 CRT I/O 函数的行为相同,因此我建议您仔细查看 MSDN 和 CResourceCabinetT
模板。
其他功能
还有几个函数我还没有讨论过,但它们仍然很重要。本节专门介绍这些函数。对于 CCabinetT
,这些函数是:
AbortOperation
GetLastError
函数 AbortOperation
可以从任何通知函数(OnCabinetInfo
、OnCopyFile
、OnCopyFileComplete
和 OnNextCabinet
)调用。它将立即中止 Copy
操作,所有未提取的文件将不再提取,已提取的文件将保持提取状态。GetLastError
函数可用于检查内部错误结构。有关详细信息,请参阅 Cabinet SDK 文档。可能某个函数失败了,但 GetLastError
不会返回错误。在这种情况下,请尝试使用 Win32 GetLastError
函数。如果这没有提供有效的错误,那么很可能您错误地使用了该类或发生了内存不足的情况。对于 CCabinetExT
模板,有一个函数需要描述:
BuildExName
此函数保证您将获得一个可以传递给 Copy
函数的字符串,以便它能够检测您的扩展。该函数的用法很简单,如果您不理解,可以查看注释以了解如何使用它。
最后但并非最不重要:CResourceCabinetT
此模板基于 CCabinetExT
模板。它提供了直接从模块的资源部分提取 Cabinet 文件的方法。使用它非常简单
class CTutorialCabinet : public CResourceCabinetT<CTutorialCabinet> { };
这应该看起来很熟悉!用法与我上面描述的 CTutorialCabinet
类完全相同。事实上,上述的 CTutorialCabinet
实现也适用于这个类!然而,为了使编程更简单,并消除构建 Copy
字符串的需要,有三个辅助函数可以为您完成此操作
BuildResourceName
CopyFromResource
IsResourceCabinet
BuildResourceName
函数从资源标识符和资源类型为您构建一个兼容的 Copy
字符串。CopyFromResource
和 IsResourceCabinet
函数通过自动化构建兼容的 Copy
字符串,将这项工作从您手中接管。演示项目提供了一个使用 CResourceCabinetT
模板的工作示例和可执行文件。
最后说明
- 要编译演示项目,您需要从本文顶部的链接下载 Cabinet SDK。除非您将项目与 Cabinet SDK 链接或包含源文件,否则演示项目将无法编译。我故意没有包含它们,因为我不知道是否允许重新分发它们。
- 此实现使用了
stdext::<CODE>hash_set
类。如果您没有 Visual Studio .NET 2003,您可以相当容易地更改CCabinetExT
的实现,以使用std::set
代替。 - 这些类不是线程安全的。但是,每个线程都可以有自己的上下文。由于每个类都使用自己的上下文,因此为每个单独的线程使用一个类的实例是完全安全的。只需确保您不要交叉使用这些实例。
- 这些模板可供所有希望使用它们的人自由使用,即使在商业应用程序中也是如此。但请注意,可能仍然存在一些错误。如果您遇到任何错误,请告诉我,我将尽力尽快修复。
修订历史
- 2004 年 7 月 25 日:首次发布。