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

Zipper 组件

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.76/5 (16投票s)

2006年2月27日

CPOL

7分钟阅读

viewsIcon

98196

downloadIcon

1672

一个用于文件压缩和解压缩的 COM / ATL 组件。

引言

如今,文件压缩和解压缩是必要的,以改进多项任务,例如服务器之间传输文件。市面上有许多 API 允许在程序中添加压缩/解压缩功能。最近,我需要一个可以在 C++ 和 VBS 中使用的组件,所以我开始研究一些 Zip API。我在Lucian Wischik 的文章中找到了一个很棒的。然而,它是一个用 C/C++ 编写的 API,不能在 VBS 中使用。所以,我决定围绕这个 API 做一个封装,并创建了一个基于 ATL 的 COM 组件。

背景

我正在使用的 API 干净、简单、优雅,并且不需要任何外部依赖。我发现这个 API 非常有用,并且之前确实在我的一个项目中使用过它。作为背景知识,你可以查阅Lucian Wischik 的文章,这样你就可以了解该 API 的工作原理。归根结底,这个组件只是该 API 的一个封装。

我决定使用 ATL 编写组件有两个原因。首先,我喜欢 ATL。其次,我不喜欢 COM。使用 ATL 确保我不需要处理 COM 本身。此外,我使用了 AppWizard 和 ATL Wizard 来生成所有代码。因此,我只公开了与属性和方法实现相关的代码。我使用 Visual C++ 6.0 测试了该组件,但用更新版本的 Visual C++ 编译它应该不是问题。

设计理念

首先,在展示代码之前,我想做一些评论。使用该组件有两种方式:创建 Zip 文件,以及从现有 Zip 文件中解压缩文件。以下是创建 Zip 文件的流程

几点说明:创建组件后,我们必须填写许多属性(即输入路径、文件名、输出路径等),以便能够创建 Zip 文件。然后,我们必须告诉组件添加任意数量的文件。注意:如果找不到文件,将抛出运行时异常。当我们添加文件时,它不会创建 zip 文件,而只会保留对该文件的引用(目录路径和文件名)。这是因为一旦你创建了一个 zip 文件,你就不能再添加另一个文件(这是由于原始 API 的设计)。所以,你必须添加任意数量的文件,这些文件将包含在 zip 文件中。完成此操作后,你必须调用将创建包含所有先前添加的文件的 Zip 文件的方法。注意:如果在添加文件和创建 Zip 文件的间隔时间里,硬盘上的一个源文件被删除了,那么该文件将不会被添加;但是,不会抛出运行时异常。

下表展示了解压缩文件的流程

如您所见,流程非常简单。设置 Zip 文件的属性后,您必须打开 Zip 文件。这将为组件提供有关文件的多项信息,例如每个文件的名称、压缩率、文件数量等。完成此操作后,您可以压缩一个或多个文件。之后,必须关闭 Zip 文件。

准备好了吗?好的,让我们看看代码。

实现

首先,让我们看一下协类声明。如下所示:

// ZipUtility.h : Declaration of the CZipUtility

#ifndef __ZIPUTILITY_H_
#define __ZIPUTILITY_H_

#include "resource.h"       // main symbols
#include "ZipFileInfo.h"

class ATL_NO_VTABLE CZipUtility : 
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CZipUtility, &CLSID_ZipUtility>,
    public ISupportErrorInfo,
    public IDispatchImpl<IZipUtility, 
           &IID_IZipUtility, &LIBID_ZIPPERLib>
{
    public:
        CZipUtility()
        {
        }

        DECLARE_REGISTRY_RESOURCEID(IDR_ZIPUTILITY)

        DECLARE_PROTECT_FINAL_CONSTRUCT()

        BEGIN_COM_MAP(CZipUtility)
            COM_INTERFACE_ENTRY(IZipUtility)
            COM_INTERFACE_ENTRY(IDispatch)
            COM_INTERFACE_ENTRY(ISupportErrorInfo)
        END_COM_MAP()

        // ISupportsErrorInfo
        STDMETHOD(InterfaceSupportsErrorInfo)(REFIID riid);

    // IZipUtility
    public:
        STDMETHOD(ExistsFile)(BSTR strFileName, BOOL* pVal);
        STDMETHOD(Open)();
        STDMETHOD(get_Password)(/*[out, retval]*/ BSTR *pVal);
        STDMETHOD(put_Password)(/*[in]*/ BSTR newVal);
        STDMETHOD(get_FullFileName)(/*[out, retval]*/ BSTR *pVal);
        STDMETHOD(Unzip)();
        STDMETHOD(Zip)();
        STDMETHOD(get_Count)(/*[out, retval]*/ long *pVal);
        STDMETHOD(AddFile)(BSTR strFileName, BSTR strNewName);
        STDMETHOD(get_FileName)(/*[out, retval]*/ BSTR *pVal);
        STDMETHOD(put_FileName)(/*[in]*/ BSTR newVal);
        STDMETHOD(get_OutputPath)(/*[out, retval]*/ BSTR *pVal);
        STDMETHOD(put_OutputPath)(/*[in]*/ BSTR newVal);
        STDMETHOD(get_InputPath)(/*[out, retval]*/ BSTR *pVal);
        STDMETHOD(put_InputPath)(/*[in]*/ BSTR newVal);

    private:
        _bstr_t m_bstrFileName;
        _bstr_t m_bstrOutputPath;
        _bstr_t m_bstrInputPath;
        _bstr_t m_bstrPassword;
        ZipFileInfoVtr m_vtrFiles;

        _bstr_t CalcFullFileName();
};

#endif //__ZIPUTILITY_H_

这是类的声明。现在让我们看看组件的属性。

  • InputPath:获取或设置将从中获取文件的源目录。
    STDMETHODIMP CZipUtility::get_InputPath(BSTR *pVal)
    {
        *pVal = m_bstrInputPath.copy();
    
        return S_OK;
    }
    
    STDMETHODIMP CZipUtility::put_InputPath(BSTR newVal)
    {
        m_bstrInputPath = newVal;
    
        return S_OK;
    }
  • OutputPath:获取或设置提取文件将放置的目录路径。
    STDMETHODIMP CZipUtility::get_OutputPath(BSTR *pVal)
    {
        *pVal = m_bstrOutputPath.copy();
    
        return S_OK;
    }
    
    STDMETHODIMP CZipUtility::put_OutputPath(BSTR newVal)
    {
        m_bstrOutputPath = newVal;
    
        return S_OK;
    }
  • FileName:获取或设置 ZIP 文件的名称。
    STDMETHODIMP CZipUtility::get_FileName(BSTR *pVal)
    {
        *pVal = m_bstrFileName.copy();
    
        return S_OK;
    }
    
    STDMETHODIMP CZipUtility::put_FileName(BSTR newVal)
    {
        m_bstrFileName = newVal;
    
        return S_OK;
    }
  • Password:获取或设置用于打开或锁定 Zip 文件的密码。
    STDMETHODIMP CZipUtility::get_Password(BSTR *pVal)
    {
        *pVal = m_bstrPassword.copy();
    
        return S_OK;
    }
    
    STDMETHODIMP CZipUtility::put_Password(BSTR newVal)
    {
        m_bstrPassword = newVal;
    
        return S_OK;
    }

到目前为止,我想一切都清楚了。请注意,这些值只是简单地从局部变量中获取/存储。以下是只读属性,没什么特别的。

  • Count:返回 Zip 文件包含的文件数量。
    STDMETHODIMP CZipUtility::get_Count(long *pVal)
    {
        *pVal = (long)m_vtrFiles.size();
    
        return S_OK;
    }
  • FullFileName:获取 Zip 文件的完整限定名(与 OutputFile + "\" + FileName 相同)。
    STDMETHODIMP CZipUtility::get_FullFileName(BSTR *pVal)
    {
        *pVal = CalcFullFileName().copy();
    
        return S_OK;
    }

再说一次,没什么特别的。不过,看看 `Count` 属性。请注意,它获取 `std::vector` 的 `size()`。也许是时候介绍 `ZipInfoFile` 结构了。这没什么花哨的,只是一个保存与 Zip 文件相关信息的结构。

struct ZipFileInfo
{
    _bstr_t bstrFileName;
    _bstr_t bstrNewName;
    _bstr_t bstrPath;
    long lCompSize;
};

typedef std::vector<ZipFileInfo> ZipFileInfoVtr;

现在,让我们看看我们的第一个重要方法。`AddFile` 方法添加对将被压缩的文件的引用。代码如下:

STDMETHODIMP CZipUtility::AddFile(BSTR strFileName, BSTR strNewName)
{
    ZipFileInfo zipInfo;

    // can add ONLY if the zip file is being created
    zipInfo.bstrFileName = strFileName;
    zipInfo.bstrNewName = strNewName;
    zipInfo.bstrPath = m_bstrInputPath;
    m_vtrFiles.push_back(zipInfo);

    return S_OK;
}

`strFileName` 参数实际上是文件名。但是,如果需要,`strNewName` 参数允许您在 zip 文件中重命名文件。另外,请注意,提交文件的信息存储在我们的向量结构中。

如前所述,在添加了所需数量的文件后,您必须创建 Zip 文件。这通过调用 `Zip` 方法来完成。在这里,我们最终使用了 ZipUtils API。代码如下:

STDMETHODIMP CZipUtility::Zip()
{
    HZIP hZip;
    _bstr_t bstrFullFileName;

    hZip = CreateZip(CalcFullFileName(), m_bstrPassword);
    if (!hZip) return S_FALSE;

    for (ZipFileInfoVtr::iterator iterBegin = 
         m_vtrFiles.begin(); iterBegin != 
         m_vtrFiles.end(); ++iterBegin)
    {
        ZipFileInfo zipInfo = *iterBegin;

        bstrFullFileName = m_bstrInputPath + 
           _bstr_t(_T("\\")) + zipInfo.bstrFileName;
        ZipAdd(hZip, zipInfo.bstrNewName, bstrFullFileName);
    }
    CloseZip(hZip);

    return S_OK;
}

很简单,不是吗?请注意,我们首先调用 `CreateZip` 函数,该函数将返回新创建的 Zip 文件的句柄。参数是 Zip 文件的名称(实际上是全名),以及密码(如果我们想锁定它)。如果函数返回 `NULL`,我们就会抛出异常(请记住 ATL/COM 组件会将 `S_FALSE` 解释为错误)。然后,我们遍历向量,检索先前添加的文件信息。然后,我们为每个元素调用 `ZipAdd`,使 zip 文件添加这些文件。注意:如果 `ZipAdd` 失败,它将简单地不包含当前文件。最后,我们关闭句柄。

经过这些步骤,我们的新 Zip 文件就完成了。解压缩呢?好吧,设置属性后,我们必须首先调用 `Open` 方法。此方法将读取 Zip 文件的结构并检索有关它的信息。

STDMETHODIMP CZipUtility::Open()
{
    HZIP hZip;
    ZIPENTRY zipEntry;
    ZIPENTRY zipItem;

    m_vtrFiles.empty();

    hZip = OpenZip(CalcFullFileName(), m_bstrPassword);
    if (!hZip) return S_FALSE;

    GetZipItem(hZip, -1, &zipEntry);
    for (int i = 0; i < zipEntry.index; i++)
    {
        ZipFileInfo zipInfo;

        GetZipItem(hZip, i, &zipItem);
        zipInfo.bstrFileName = zipItem.name;
        zipInfo.bstrNewName = zipItem.name;
        zipInfo.lCompSize = zipItem.comp_size;
        m_vtrFiles.push_back(zipInfo);
    }
    CloseZip(hZip);

    return S_OK;
}

和以前一样,我们打开 Zip 文件。然后,我们遍历 Zip 文件的每个元素并获取其信息。然后,我们关闭 Zip 句柄。

打开 Zip 文件后,我们调用 `Unzip` 将文件解压缩到 `OutputPath` 目录中

STDMETHODIMP CZipUtility::Unzip()
{
    HZIP hZip;
    ZIPENTRY zipEntry;
    ZIPENTRY zipItem;
    _bstr_t bstrOutputFile;

    m_vtrFiles.empty();

    hZip = OpenZip(CalcFullFileName(), m_bstrPassword);
    if (!hZip) return S_FALSE;

    GetZipItem(hZip, -1, &zipEntry);
    for (int i = 0; i < zipEntry.index; i++)
    {
        ZipFileInfo zipInfo;

        GetZipItem(hZip, i, &zipItem);
        bstrOutputFile = m_bstrOutputPath + 
          _bstr_t(_T("\\")) + _bstr_t(zipItem.name);

        UnzipItem(hZip, i, bstrOutputFile);
    }
    CloseZip(hZip);

    return S_OK;
}

最后,我们有一个方法可以判断 Zip 文件中是否存在某个文件。`ExistsFile` 将遍历 Zip 的结构,如果它匹配某个文件名,则返回 `TRUE`。

STDMETHODIMP CZipUtility::ExistsFile(BSTR strFileName, BOOL* pVal)
{
    BOOL bExists = FALSE;
    _bstr_t bstrFileName = strFileName;

    for (ZipFileInfoVtr::iterator iterBegin = 
         m_vtrFiles.begin(); iterBegin != 
         m_vtrFiles.end(); ++iterBegin)
    {
        ZipFileInfo zipInfo;

        zipInfo = *iterBegin;
        if (bstrFileName == zipInfo.bstrFileName) {
            bExists = TRUE;
            break;
        }
    }

    *pVal = bExists;

    return S_OK;
}

就这些了,各位。我们如何使用这个组件呢?

使用组件

使用这个组件很简单——就像使用其他任何组件一样简单。这里,我将解释如何在 VB 脚本中使用 Zip 文件。你可以在 C#、VB 或 C++ 中以相同的方式使用它。

此脚本用于创建 Zip 文件

rem create a zip file
dim objZip

set objZip = CreateObject("ZIPPER.ZipUtility")
objZip.InputPath = "C:\Source"
objZip.OutputPath = "C:\Destiny"
objZip.FileName = "test.zip"
objZip.Password = "somePwd"
call objZip.AddFile("file_1.txt", "newfile_1.txt")
call objZip.AddFile("file_2.txt", "newfile_2.txt")
call objZip.AddFile("file_3.txt", "newfile_3.txt")
call objZip.Zip()
set objZip = nothing

此脚本用于解压缩文件

rem unzip files
dim objZip

set objZip = CreateObject("ZIPPER.ZipUtility")
objZip.InputPath = "C:\Source"
objZip.OutputPath = "C:\Destiny"
objZip.FileName = "test.zip"
objZip.Password = "somePwd"

on error goto ErrHandler
call objZip.Open()
MsgBox "There are " & objZip.Count & " files zipped"
if (not objZip.ExistsFile("file_1.txt")) then
    MsgBox "file_1.txt does not exist"
end if
call objZip.Unzip()s

ErrHandler:
set objZip = nothing

关注点

希望这对您有所帮助。有很多改进的方法。事实上,在我看来,最令人头疼的问题是,一旦 Zip 文件创建,就无法再添加文件。我当时在考虑以下方案:向现有 Zip 文件添加文件时,删除当前文件并重新创建 Zip 文件。然而,我认为这会大大降低性能,所以让组件的客户端来决定。

有几点说明。首先,请记住在使用组件之前,您必须注册它。通常,调用 `regsvr32` 是最好的方法。其次,如果您打算修改此组件并分发它,我强烈建议您更改接口和 coclass 的 IID 和 CLSID,以避免出现问题。

我一直在寻求改进,所以欢迎任何评论、批评和建议。您可以在此页面上发布它们,也可以发送电子邮件。

再见!

历史

  • [2006 年 2 月 27 日]:文章主要发布。
  • [2006 年 3 月 1 日]:更改了示例脚本中的一个小错误,并纠正了一些语法错误。
压缩组件 - CodeProject - 代码之家
© . All rights reserved.