分类: C/C++
2008-01-31 11:55:14
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 |
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:
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.
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:
The following code from demo app shows how this is done:
/////////////////////////////////////////////////////////////////////////////
// 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:
By searching for directory name, you can find all entries in that directory. Here is code in demo app that implements search:
/////////////////////////////////////////////////////////////////////////////
// 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:
The final search example shows an "anywhere" search, for file names containing "3":
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:
Here is code in demo app that implements this:
////////////////////////////////////////////////////////////////////////////
// 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.
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
}
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
To integrate CResourceZip
class into your app, do the following:
CResourceZip
.
CResourceZip
. See above for sample code. 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.
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.