Click here to Skip to main content
15,867,704 members
Articles / Desktop Programming / ATL
Article

Zipper Component

Rate me:
Please Sign up or sign in to vote.
4.76/5 (19 votes)
27 Feb 2006CPOL7 min read 94.9K   1.7K   42   23
A COM / ATL component for zipping and unzipping files.

Introduction

Compressing and decompressing files are nowadays necessary, in order to improve several tasks, such as transferring files between servers. There are many APIs out there that allows adding zip/unzip functionality to programs. Recently, I needed a component that could be used in both C++ and VBS, so I started to look at some Zip APIs. I found an excellent one in Lucian Wischik’s article. However, it was an API written in C/C++, not usable in VBS. So, I decided to do a wrapper around such an API and created an ATL-based COM component.

Background

The API I’m using is clean, simple, and elegant, and it does not require any external dependency. I found such an API quite useful, and indeed used it in one of my projects before. As a background, you could take a look into Lucian Wischik’s article so you can understand how the API works. In the end, this component is just a wrapper around such an API.

I decided to write the component using ATL for two reasons. First, I like ATL. Second, I don’t like COM. Using ATL ensures me that I don’t need to deal with COM itself. Furthermore, I used the AppWizard and the ATL Wizard to generate all the code. Hence, I’m just exposing the code related to the implementation of the properties and methods. I used and tested the component with Visual C++ 6.0, yet it shouldn’t be a problem to compile it with a newer version of Visual C++.

Design concepts

First, I’d like to make a few comments before showing the code. There are two ways of using the component: for creating Zip files, and for unzipping files from an existing Zip file. Here is the flow for creating a Zip file:

Image 1

A few comments: after creating the component, we must fill many properties (i.e. input path, file name, output path, etc.) in order to be able to create the Zip file. Then, we must tell the component to add as many files as we want. Note: if the file is not found, a runtime exception will be thrown. When we add a file, it won’t create the zip file, but it will only keep a reference to such a file (the directory path and the file name). This is so because once you create a zip file, you cannot add another file (this is due to the design of the original API). So, you’ll have to add as many files as you want to include in the zip file. Once this is done, you have to call the method that will create the Zip file with all the previously added files. Note: if in between the time when the file is added and when the Zip is created, one of the source files in hard disk is removed, such a file will not be added; however, no runtime exception will be thrown.

The following chart presents the flow for unzipping files:

Image 2

As you can see, the flow is very straightforward. Once the properties of the Zip file are set, you must open the Zip file. This will provide the component with several information about the file, such as each file’s name, the compression rate, the number of files, etc. Once this is done, you can Zip one or many files. After that, the Zip file must be closed.

Ready? Alright, let’s see the code.

Implementation

First, let us take a look at the co-class declaration. Here it goes:

// 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_

This is the declaration of the class. Let us now see the properties of the component.

  • InputPath: gets or sets the source directory from where the files will be taken.
    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: gets or sets the directory path where the extracted files will be placed.
    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: gets or sets the name of the ZIP file.
    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: gets or sets the password for opening or locking the Zip file.
    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;
    }

So far is clear, I think. Notice that the values are simply taken from / stored in local variables. The following are the read-only properties, nothing special.

  • Count: returns the number of files that a Zip file contains.
    STDMETHODIMP CZipUtility::get_Count(long *pVal)
    {
        *pVal = (long)m_vtrFiles.size();
    
        return S_OK;
    }
  • FullFileName: gets the complete qualified name of the Zip file (is the same as OutputFile + "\" + FileName).
    STDMETHODIMP CZipUtility::get_FullFileName(BSTR *pVal)
    {
        *pVal = CalcFullFileName().copy();
    
        return S_OK;
    }

Again, nothing special. Yet, take a look at the Count property. Notice that it gets the size() of a std::vector<ZipInfoFile>. Perhaps it is time to introduce the ZipInfoFile structure. This is nothing fancy, only a structure that holds the information related to the Zip file.

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

typedef std::vector<ZipFileInfo> ZipFileInfoVtr;

Now, let's take a look at our first important method. The AddFile method adds a reference to a file that will be zipped. Here is the code:

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;
}

The strFileName parameter is actually the name of the file name. But the strNewName parameter allows you to rename the file within the zip, if desired. Also, notice that the information of the submitted file is saved within our vector structure.

As mentioned earlier, after adding as many files as desired, you must create the Zip file. This is done by calling the Zip method. Here, we finally use the ZipUtils API. Here is the code:

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;
}

Simple, isn't it? Notice that first, we call the CreateZip function, that will return us a handle to the newly created Zip. The parameters are the name of the Zip file (full name indeed), and the password, if we want to lock it. If the function returns NULL, we throw an exception (remember that the ATL/COM component will interpret S_FALSE as an error). Then, we iterate over the vector, and retrieve the information about the previously added files. Then, we call ZipAdd for each element, causing the zip file to add such files. Note: if ZipAdd fails, it simply won't include the current file. Finally, we close the handle.

After these steps, we are now done with our new Zip file. What about unzipping? Well, after setting the properties, we have to first call the Open method. This method will read the structure of the Zip file and retrieve information about it.

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;
}

Like before, we open the Zip file. Then, we iterate over each element of the Zip file and get its information. Then, we close the Zip handle.

After opening the Zip file, we call Unzip for unzipping the files into the OutputPath directory:

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;
}

Finally, we have a method that allows to determine whether a file exists within a Zip file or not. ExistsFile will iterate over the Zip's structure, and if it matches the name of a file, it will return 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;
}

That is all folks. How do we use the component?

Using the component

Using the component is easy - as easy as using any other component. Here, I'll explain how to use the Zip file in a VB script. You can use it in the same way in C#, VB, or C++.

This script is for creating a Zip file:

VB
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

This script is for unzipping a file:

VB
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

Points of interest

Hope this is fine to you. There are many ways of improvement. Indeed, the most annoying problem -in my opinion- is that once the Zip file has been created, you cannot add files. I was thinking on the following: when adding a file to an existing Zip, delete the current file and re-create the Zip. Yet, I think this will truly slow down performance, so I let the client of the component to decide.

A couple of notes. First, remember that before using the component, you must register it. Usually, calling regsvr32 is the best way. Second, if you intend to modify this component and distribute it, I strongly recommend you to change both the IID and CLSID of the interface and the coclass, in order to avoid problems.

I am always looking for improvements, so I welcome any comments, criticisms, and suggestions. You can post them in this page, and you also can email.

Farewell!

History

  • [Feb 27, 2006]: Main release of the article.
  • [Mar 01, 2006]: Changed a minor bug in the example script, as well as corrected a few grammar errors.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Chief Technology Officer Blendwerk TI & Media
Mexico Mexico
42

Comments and Discussions

 
GeneralRe: good, but... Pin
Fernando A. Gomez F.6-Mar-06 14:12
Fernando A. Gomez F.6-Mar-06 14:12 
GeneralRe: good, but... Pin
jhlee05266-Mar-06 15:14
jhlee05266-Mar-06 15:14 
GeneralRe: good, but... Pin
Fernando A. Gomez F.6-Mar-06 16:55
Fernando A. Gomez F.6-Mar-06 16:55 
GeneralRe: good, but... Pin
jhlee05266-Mar-06 17:48
jhlee05266-Mar-06 17:48 
GeneralNice Pin
Bitym1-Mar-06 5:17
Bitym1-Mar-06 5:17 
GeneralRe: Nice Pin
Fernando A. Gomez F.1-Mar-06 5:30
Fernando A. Gomez F.1-Mar-06 5:30 
GeneralExcellent Pin
mehrcpp1-Mar-06 2:41
mehrcpp1-Mar-06 2:41 
GeneralRe: Excellent Pin
Fernando A. Gomez F.1-Mar-06 3:54
Fernando A. Gomez F.1-Mar-06 3:54 
GeneralZipper Pin
obinna_eke28-Feb-06 2:11
obinna_eke28-Feb-06 2:11 
GeneralRe: Zipper Pin
Fernando A. Gomez F.28-Feb-06 4:16
Fernando A. Gomez F.28-Feb-06 4:16 
GeneralRe: Zipper Pin
Fernando A. Gomez F.28-Feb-06 7:50
Fernando A. Gomez F.28-Feb-06 7:50 
GeneralFinlay a Zip Component! Pin
JOHN1127-Feb-06 20:21
JOHN1127-Feb-06 20:21 
GeneralRe: Finlay a Zip Component! Pin
Fernando A. Gomez F.28-Feb-06 4:10
Fernando A. Gomez F.28-Feb-06 4:10 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.