Chinaunix首页 | 论坛 | 博客
  • 博客访问: 5326297
  • 博文数量: 671
  • 博客积分: 10010
  • 博客等级: 上将
  • 技术积分: 7310
  • 用 户 组: 普通用户
  • 注册时间: 2006-07-14 09:56
文章分类

全部博文(671)

文章存档

2011年(1)

2010年(2)

2009年(24)

2008年(271)

2007年(319)

2006年(54)

我的朋友

分类: C/C++

2008-01-31 11:55:14

Introduction

This series of articles discusses three typical requirements when working with file resources (by that I mean, files that have been compiled into your program as resources):

Text and binary files
Part 2 Zip files
Encrypted files

Embedding a Zip File as a Resource

In , I covered some general topics about resources, and presented CResourceFile and CResourceTextFile, classes that allow you to read binary and text files that have been embedded as resources in your app. In this article, I will talk about CResourceZip, a class that allows you to read, search, and unzip entries in a zip file.

For this article's demo program, I have set up a resource to include a zip file. Here are actual lines taken from XResFileTest.rc:

/////////////////////////////////////////////////////////////
//
// BINARY
//

IDU_ZIP            BINARY  DISCARDABLE     "test_files.zip"

For a detailed description of this Resource Compiler directive, please see .

The contents of zip file are shown below:

screenshot

All of the files in the zip are text files, containing one, two, or three lines of text - 15 characters plus (sometimes) a carriage-return/line-feed. Just by looking at file sizes, it is easy to guess that test3c.txt must be Unicode.

Working with Zip Files

CResourceZip is derived from CResourceFile (see ) and uses the XUnzip code from my article .

The demo app allows you to list contents of embedded zip file:

screenshot

The following code from demo app shows how this is done:

Collapse
/////////////////////////////////////////////////////////////////////////////
// OnListEntries
void CXResFileTestDlg::OnListEntries() 
{
    m_List.Printf(CXListBox::Blue, CXListBox::White, 0, _T(""));
    m_List.Printf(CXListBox::Blue, CXListBox::White, 0, 
        _T("=== Listing zip entries ==="));

    BOOL rc = FALSE;
    
    CResourceZip rz;


    rc = rz.Open(NULL, _T("IDU_ZIP"));

    if (rc)
    {
        m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
            _T("\tZip resource opened OK"));

        int nCount = rz.GetCount();

        if (nCount == ZIP_ENTRIES)
        {
            m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
                _T("\tZip resource contains %d entries"), nCount);

            CResourceZip::ZipEntryData zed = { 0 };
            CString strType = _T("");

            for (int i = 0; i < nCount; i++)
            {
                rz.GetEntry(i, &zed);
                if (rz.IsDirectory(zed))
                    strType = _T("Dir");
                else
                    strType = _T("File");
                m_List.Printf(CXListBox::Black, CXListBox::White, 0, 
                    _T("\t\t%4d:\t%s\t%s"), i, strType, zed.name);
            }
        }
        else
        {
            m_List.Printf(CXListBox::Red, CXListBox::White, 0, 
                _T("\tZip resource contains %d entries, incorrect count"), 
                nCount);
        }

        rz.Close();
        m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
            _T("\tZip resource closed"));
    }
    else
    {
        m_List.Printf(CXListBox::Red, CXListBox::White, 0,
            _T("Failed to open zip resource IDU_ZIP"));
    }
}

In above code, the key lines are highlighted. First CResourceZip::Open() is used to open zip resource, then CResourceZip::GetCount() is called to get the number of zip entries, and finally CResourceZip::GetEntry() is called to get zip entry and display the entry name.

CResourceZip provides file name search capability via the functions CResourceZip::FindFirst() and CResourceZip::FindNext(). In the following screenshot, the prefix string "test files/temp1/test2/temp3/" is searched for:

screenshot

By searching for directory name, you can find all entries in that directory. Here is code in demo app that implements search:

Collapse
/////////////////////////////////////////////////////////////////////////////
// OnFindEntries
void CXResFileTestDlg::OnFindEntries() 
{
    BOOL rc = FALSE;
    CResourceZip rz;

    UpdateData(TRUE);

    CString strSearchType = _T("");

    switch ((CResourceZip::SearchType) m_nSearchType)
    {
        default:
        case CResourceZip::prefix:
            strSearchType = _T("prefix");
            break;

        case CResourceZip::suffix:
            strSearchType = _T("suffix");
            break;

        case CResourceZip::any:
            strSearchType = _T("anywhere");
            break;
    }

    if (m_strFind.IsEmpty())
    {
        AfxMessageBox(_T("Please enter a file or directory to find."));
        return;
    }

    m_List.Printf(CXListBox::Blue, CXListBox::White, 0, _T(""));
    m_List.Printf(CXListBox::Blue, CXListBox::White, 0, 
        _T("=== Finding zip entries ==="));

    m_List.Printf(CXListBox::Black, CXListBox::White, 0, 
        _T("\tLooking for entries matching '%s' (%s)"), m_strFind, 
        strSearchType);

    rc = rz.Open(NULL, _T("IDU_ZIP"));

    if (rc)
    {
        m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
            _T("\tZip resource opened OK"));

        int nCount = rz.GetCount();

        if (nCount == ZIP_ENTRIES)
        {
            CResourceZip::ZipEntryData zed = { 0 };

            rc = rz.FindFirst(m_strFind, (CResourceZip::SearchType) 
                        m_nSearchType, &zed);

            int nFound = 0;

            while (rc)
            {
                m_List.Printf(CXListBox::Black, CXListBox::White, 0, 
                    _T("\t\t%4d:\t%s"), zed.index, zed.name);
                nFound++;
                rc = rz.FindNext(&zed);
            }

            if (nFound > 0)
            {
                m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
                    _T("\tFound %d entries matching '%s' (%s)"), 
                    nFound, m_strFind, strSearchType);
            }
            else
            {
                CString strFmt = _T("\t\tCannot locate '%s' as %s");

                if ((CResourceZip::SearchType) m_nSearchType == 
                    CResourceZip::any)
                    strFmt = _T("\t\tCannot locate '%s' %s");

                m_List.Printf(CXListBox::Red, CXListBox::White, 0, strFmt, 
                    m_strFind, strSearchType);
            }
        }
        else
        {
            m_List.Printf(CXListBox::Red, CXListBox::White, 0, 
                _T("\tZip resource contains %d entries, incorrect count"), 
                    nCount);
        }

        rz.Close();
        m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
            _T("\tZip resource closed"));
    }
    else
    {
        m_List.Printf(CXListBox::Red, CXListBox::White, 0,
            _T("Failed to open zip resource IDU_ZIP"));
    }
}

The above code relies on CResourceZip::FindFirst() to initialize the search:

/////////////////////////////////////////////////////////////////////////////
//
// FindFirst()
//
// Purpose:     Finds first zip entry matching lpszFile, initializes for
//              subsequent calls to FindNext().  No wildcards are supported.
//
// Parameters:  lpszFile - pointer to full or partial file name
//              eSearch  - specifies how to search:
//                           prefix - lpszFile should be matched with start
//                                    of name stored in zip
//                           suffix - should be matched with end of
//                                    name stored in zip
//                           any    - should be matched anywhere in the
//                                    named stored in zip
//                         All matches are case-insensitive.
//              zed      - pointer to data struct that receives info
//
// Returns:     BOOL     - returns TRUE if matching zip entry found
//
BOOL CResourceZip::FindFirst(LPCTSTR lpszFile, 
                             SearchType eSearch, 
                             ZipEntryData *zed)

The SearchType enum is defined as:

    enum SearchType
    {
        prefix = 0,                    // match prefix only
        suffix,                        // match suffix only
        any                            // match anywhere
    };

This allows you to specify partial file matches, by searching only at the beginning of file name, only at the end, or anywhere in the name. Note that when I use the term "file name", I mean what is stored in ZipEntryData::name member, which is usually a relative path name.

This example shows searching for a suffix, which is handy when you are looking for files:

screenshot

The final search example shows an "anywhere" search, for file names containing "3":

screenshot

The third option in the demo app is to display the contents of a zip entry. After selecting zip entry, press View Contents and you will see:

screenshot

Here is code in demo app that implements this:

Collapse
////////////////////////////////////////////////////////////////////////////
// OnViewContents
void CXResFileTestDlg::OnViewContents() 
{
    BOOL rc = FALSE;
    CResourceZip rz;

    UpdateData(TRUE);

    m_List.Printf(CXListBox::Blue, CXListBox::White, 0, _T(""));
    m_List.Printf(CXListBox::Blue, CXListBox::White, 0, 
        _T("=== Displaying zip entry %d ==="), m_nViewIndex);

    rc = rz.Open(NULL, _T("IDU_ZIP"));

    if (rc)
    {
        m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
            _T("\tZip resource opened OK"));

        int nCount = rz.GetCount();

        if (nCount == ZIP_ENTRIES)
        {
            if (m_nViewIndex >= nCount)
            {
                CString str = _T("");
                str.Format(_T("Please enter a number between 0 and %d"), 
                    nCount-1);
                AfxMessageBox(str);
                return;
            }

            CResourceZip::ZipEntryData zed = { 0 };
            if (rz.GetEntry(m_nViewIndex, &zed))
            {
                if (!rz.IsDirectory(zed))
                {
                    BYTE * buf = rz.UnZip(m_nViewIndex);

                    if (buf)
                    {
                        m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
                            _T("\tOpened entry %d: '%s'"), m_nViewIndex, 
                            zed.name);

                        // we use CResourceTextFile to read lines from the 
                        // text file
                        CResourceTextFile rf;
                        CResourceTextFile::ConvertAction eConvert = 
                            CResourceTextFile::NoConvertAction;
    #ifdef _UNICODE
                        eConvert = CResourceTextFile::ConvertToUnicode;
    #endif
                        if (m_nViewIndex == 6)        // entry 6 is a Unicode
                                                      // file
    #ifdef _UNICODE
                            eConvert = CResourceTextFile::NoConvertAction;
    #else
                            eConvert = CResourceTextFile::ConvertToAnsi;
    #endif
                        rf.SetTextBuffer((TCHAR *)buf, zed.unc_size/sizeof(
                            TCHAR), eConvert);
                        
                        int nLine = 0;
                        CString strLine = _T("");

                        while (!rf.IsAtEOF())
                        {
                            nLine++;
                            strLine.Format(_T("This is line %d."), nLine);
                            TCHAR s[100] = { _T('\0') };
                            int nLen = rf.ReadLine(s, sizeof(s)/sizeof(
                                TCHAR)-1);

                            m_List.Printf(CXListBox::Black, CXListBox::White,
                                0, _T("\t\t%d:  length=%d  <%s>"), 
                                nLine, nLen, s);
                        }

                        free(buf);
                    }
                    else
                    {
                        m_List.Printf(CXListBox::Red, CXListBox::White, 0, 
                            _T("\tUnZip() failed for %d"), m_nViewIndex);
                    }
                }
                else
                {
                    m_List.Printf(CXListBox::Red, CXListBox::White, 0, 
                        _T("\t\tEntry %d is a directory"), m_nViewIndex);
                }
            }
            else
            {
                CString str = _T("");
                str.Format(_T("Entry %d is a directory.\r\nPlease" + 
                    "select a file."), m_nViewIndex);
                AfxMessageBox(str);
                return;
            }
        }
        else
        {
            m_List.Printf(CXListBox::Red, CXListBox::White, 0, 
                _T("\tZip resource contains %d entries, incorrect count"), 
                nCount);
        }

        rz.Close();
        m_List.Printf(CXListBox::Green, CXListBox::White, 0, 
            _T("\tZip resource closed"));
    }
    else
    {
        m_List.Printf(CXListBox::Red, CXListBox::White, 0,
            _T("Failed to open zip resource IDU_ZIP"));
    }
}

Note that CResourceTextFile is used to read lines from a zip entry. By passing the buffer pointer returned by CResourceZip::UnZip() to CResourceTextFile::SetTextBuffer(), the zip resource file can be opened, an entry unzipped, and the contents of that entry retrieved, all without having to write anything to disk.

Summary: Reading Zip Files

The code presented above can be boiled down to:

    CResourceZip rz;
    if (rz.Open(NULL, _T("IDU_ZIP")))   // open zip resource
    {
        BYTE * p = rz.UnZip(index);     // unzip entry
        if (p)
        {
            --- do something ---
        }
        .
        .
        .
        // rz goes out of scope and resource file is closed
    }

CResourceZip Quick Reference

Here is complete list of functions available in CResourceZip:

//   Close()             Close a file resource
//   DetachByteBuffer()  Return pointer to byte buffer and set flag so buffer
//                       will not be deleted when resource is closed
//   FindFirst()         Find first file that matches
//   FindNext()          Find next file that matches
//   GetByteBuffer()     Get pointer to zip resource buffer
//   GetCount()          Get number of entries in zip
//   GetEntry()          Get info for zip entry
//   GetLength()         Get length of zip resource
//   IsOpen()            Check if zip resource is open
//   Open()              Open a zip resource
//   SetByteBuffer()     Set a new buffer for the zip file resource
//   UnZip()             Unzip an entry

How to Add CResourceZip to Your Project

To integrate CResourceZip class into your app, do the following:

  1. You first need to add following files to your project:
    • ResourceFile.cpp
    • ResourceFile.h
    • ResourceZip.cpp
    • ResourceZip.h
    • XString.cpp
    • XString.h
    • XUnzip.cpp
    • XUnzip.h
  2. In Visual Studio settings, select Not using pre-compiled header for ResourceFile.cpp, ResourceZip.cpp, XString.cpp, and XUnzip.cpp.
  3. Next, include header file ResourceZip.h in source file where you want to use CResourceZip.
  4. Now you are ready to start using CResourceZip. See above for sample code.

Implementation Notes

CResourceZip has been implemented using C++, without any MFC or STL. It has been compiled under both VS6 and VS2005, and has been tested on XP and Vista. It should also work on Win2000 and Win98, but has not been tested on those platforms.

Summary

I have presented a class that allows you to unzip an entry in a zip resource file, and access the contents of that entry via memory buffer. Using the CResourceTextFile class I presented in , you read text file entries line by line, just like you can with disk files.

Embedding zip files as resources provides a significant improvement in terms of protecting your resources from being ripped, since there is no obvious indication that the resource is actually a zip file. In the next article, I will discuss possibility of encrypting resources.

阅读(1018) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~