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

Microsoft Cabinet 模板

starIconstarIconstarIconstarIconstarIcon

5.00/5 (14投票s)

2004年7月24日

10分钟阅读

viewsIcon

96074

downloadIcon

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++ newdelete 运算符。您可以像这样重写它们

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 可以从任何通知函数(OnCabinetInfoOnCopyFileOnCopyFileCompleteOnNextCabinet)调用。它将立即中止 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 字符串。CopyFromResourceIsResourceCabinet 函数通过自动化构建兼容的 Copy 字符串,将这项工作从您手中接管。演示项目提供了一个使用 CResourceCabinetT 模板的工作示例和可执行文件。

最后说明

  • 要编译演示项目,您需要从本文顶部的链接下载 Cabinet SDK。除非您将项目与 Cabinet SDK 链接或包含源文件,否则演示项目将无法编译。我故意没有包含它们,因为我不知道是否允许重新分发它们。
  • 此实现使用了 stdext::<CODE>hash_set 类。如果您没有 Visual Studio .NET 2003,您可以相当容易地更改 CCabinetExT 的实现,以使用 std::set 代替。
  • 这些类不是线程安全的。但是,每个线程都可以有自己的上下文。由于每个类都使用自己的上下文,因此为每个单独的线程使用一个类的实例是完全安全的。只需确保您不要交叉使用这些实例。
  • 这些模板可供所有希望使用它们的人自由使用,即使在商业应用程序中也是如此。但请注意,可能仍然存在一些错误。如果您遇到任何错误,请告诉我,我将尽力尽快修复。

修订历史

  • 2004 年 7 月 25 日:首次发布。
© . All rights reserved.