Zipper 组件






4.76/5 (16投票s)
一个用于文件压缩和解压缩的 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
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 日]:更改了示例脚本中的一个小错误,并纠正了一些语法错误。