Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Encapsulating MFC's CFileFind Class


May 2002/Encapsulating MFC’s CFileFind Class

If your project ever needs to search a drive for a file or set of files, you’ll find that MFC’s CFileFind class simplifies this process. CFileFind does a good job of encapsulating the Windows API functions FindFirstFile and FindNextFile and the WIN32_FIND_DATA structure. When you use CFileFind, you’ll end up writing code like this:

// Search for all files on the C: drive
CFileFind finder;
BOOL bWorking = finder.FindFile(_T("C:\\*.*"));
while (bWorking)
{
     bWorking = finder.FindNextFile();
     // Skip . and .. entries
     if (finder.IsDots()) continue;
     // Watch for directory entries
     if (finder.IsDirectory())
         {
         // Do something with this directory

         continue;
      }

     // This must be a file
     CString sFilePath(finder.GetFilePath());

     // Do something with this file
}
finder.Close();

If you want to search directories beneath the root directory, you’ll end up placing the previous code in a function that is called recursively (let’s call it RecursiveFileFind) and you’ll flesh out the “Do something with this directory” section with a call to RecursiveFileFind.

The hard work is writing the code for the “Do something with this file” section. If you are interested only in .EXE and .DLL files, for example, you must extract the file extension from sFilePath and ignore all files with other extensions.

After writing a couple of file search functions that involved recursive calls to CFileFind, I decided there had to be an easier way of doing all of this. What I wanted was something that looked like a “for” loop that avoided recursive function calls. I ended up writing a class called CFileFindDriver, defined in Listing 1 and 2 (CFileFindDriver.H and CFileFindDriver.CPP, respectively).

If you want to search for all .EXE and .DLL files on the C: drive, use the CFileFindDriver class as shown in Example 1. CFileFindDriver has a second constructor that simplifies life if you’re searching for only one type of file. For example, if you need only .EXE files in the C:\WINDOWS directory, you can use the “for” statement in Example 2.

CFileFindDriver Internals

The heart and soul of the CFileFindDriver class is the m_searchList member variable. It is declared as follows:

CTypedPtrList<CPtrList, CSearchObject*> m_searchList;
This member variable is derived from MFC’s CTypedPtrList template class. It manages a dynamic list of pointers to objects derived from the base class CSearchObject. During the course of a search, objects are added to and removed from the tail of this list. The tail entry in this list is always the “current search object” and a search is complete only when all entries have been removed from m_searchList.

The lion’s share of managing m_searchList is done by CFileFindDriver::Next. Its “while (bSearching)” loop uses the current search object at the tail of m_searchList to locate the next file that matches one of the search strings (e.g., *.EXE, *.DLL) that was specified during construction. This loop is executed several times if a search for the next file involves recursing into one or more directories.

As a search proceeds, three types of objects are added to and removed from the tail of m_searchList, see Figure 1. These objects correspond to the three different stages of the search process, described by the following pseudo-code:

Loop 1:
For each folder encountered
during the search process
{
    Loop 2:
    For each entry in the file spec array
   {
        Loop 3:
        For each file matching this file spec
        {
            Return address of CFileFind object
        }
    }
}

A CSearchForFolders object takes care of searching for folders (Loop 1). Although it keeps track of the total number of files found in each folder, it does not locate those files that match one of the entries in the file spec array; that job is the responsibility of the CSearchForFiles class, described later. Each time it encounters a new sub-directory, it returns the value “entryIsFolder” to CFileFindDriver::Next, which in turn appends a new CSearchForFolders object to the tail of m_searchList, making this new object the “current search object”.

After processing all sub-directories in a directory, the CSearchForFolders object returns the value “noMoreFolders” to CFileFindDriver::Next, which in turn removes the depleted CSearchForFolders object from the tail of m_searchList, appends a new CSearchForFileSpec object to the tail of m_searchList, and makes this new object the “current search object”.

A CSearchForFileSpec object takes care of iterating through each of the entries in m_sFileSpecArray (Loop 2). This member variable is a copy of the array of search strings (e.g., *.EXE, *.DLL) that was specified during CFileFindDriver construction. If you are searching for only one type of file, m_sFileSpecArray has only one entry and CSearchForFileSpec iterates only once. For each entry in m_sFileSpecArray, this object returns the value “entryIsFileSpec” to CFileFindDriver::Next, which in turn appends a new CSearchForFiles object to the tail of m_searchList, making this new object the “current search object”.

After processing all entries in m_sFileSpecArray, the CSearchForFileSpec object returns the value “noMoreFileSpecs” to CFileFindDriver::Next, which in turn removes the depleted CSearchForFileSpec object from the tail of m_searchList. The object that is now at the tail of m_searchList, if any, becomes the “current search object”.

A CSearchForFiles object takes care of searching for files (Loop 3). It ignores entries that are sub-directories; a CSearchForFolders object has already processed these entries. For each file that matches m_sSearchSpec, it returns the value “entryIsFile” to CFileFindDriver::Next, which in turn saves the current state of the search process and returns control to the “for” loop that is driving the search.

Back in our top-level “for” loop, we can call CFileFindDriver::GetFileFind() to retrieve the address of the CFileFind object associated with the tail entry in m_searchList (the “current search object”). This function returns NULL at the end of a search process and is therefore used to terminate the “for” loop. By retrieving the address of a CFileFind object, code in our “for” loop can call any of CFileFind’s “Attribute” member functions (any member function associated with a const CFileFind object). For example:

// Retrieve fully qualified path
CString sFilePath(driver.GetFileFind()
                 ->GetFilePath());

// Retrieve file length
DWORD dwLength = driver.GetFileFind()
                ->GetLength();

// Retrieve file creation date/time
CTime tmCreation;
driver.GetFileFind()
             ->GetCreationTime(tmCreation);

After processing all matching files in a specific directory, the CSearchForFiles object returns the value “noMoreFiles” to CFileFindDriver::Next, which in turn removes the depleted CSearchForFiles object from the tail of m_searchList. The object that is now at the tail of m_searchList (always a CSearchForFileSpec object) becomes the “current search object”.

The CFileFindDriver class takes care of a couple of additional housekeeping chores. The first is to avoid returning the same file more than once to our “for” loop. How can this happen, you may ask? Here’s an example. Suppose we provide the following search specifications:

CStringArray sFileSpecArray;
sFileSpecArray.SetSize(2);
sFileSpecArray.SetAt(0, _T("*.TXT"));
sFileSpecArray.SetAt(1, _T("ReadMe.*"));

Obviously, the file “ReadMe.TXT” matches both search specifications and, unless we watch for this, our CSearchForFiles object will return the same file twice. CFileFindDriver’s member variables m_foundList and m_iSpecArrayPos are used to circumvent this problem.

Secondly, the CFileFindDriver class keeps a list of all directories searched since the start of the current search process in its member variable m_sSearchDirList and a count of the number of matching files retrieved so far in m_iFilesFound. These variables are retrieved by the member functions GetSearchDirList and GetFilesFound, respectively. Although these two member variables and two functions are not critical to CFileFindDriver’s search operation, you might find them helpful in real life applications.

C++ Features Used by CFileFindDriver

CFileFindDriver and its helper classes CSearchObject, CSearchForFolders, CSearchForFileSpec, and CSearchForFiles illustrate many interesting features of the C++ language.

CSearchObject is an abstract base class because it has two pure virtual functions, GetPathPrefix and GetNext. Every class derived from CSearchObject must define these two functions. You cannot create an object of type CSearchObject; however, you can use pointers and references to CSearchObject, see Figure 2.

Because CSearchObject is the base class for CSearchForFolders, CSearchForFileSpec, and CSearchForFiles, its virtual functions allow us to treat collections of these classes polymorphically, just as we do when we process CFileFindDriver’s m_searchList. In the CFileFindDriver::Next function, we access the tail entry in m_searchList as follows:

 // Access the tail of our m_searchList
 CSearchObject* pEntry = m_searchList.GetTail();

The object pEntry is never of type CSearchObject; it is always one of the three derived types. By calling the virtual GetNext function, polymorphism “does the right thing” and calls one of the GetNext functions defined in CSearchForFolders, CSearchForFileSpec, or CSearchForFiles, according to the object’s vtable.

const int iResponse = pEntry->GetNext(sEntry);

Other calls to virtual functions access the appropriate object type, including the function call that finalizes the value returned by CFileFindDriver::Next:

m_pFileFind = pEntry->GetFileFind();

It is important that CSearchObject’s class destructor is declared as virtual. If this is not the case, functions like CFileFindDriver::EmptySearchList invoke only CSearchObject’s destructor each time an object attached to m_searchList is deleted; any special object cleanup in the CSearchForFolders, CSearchForFileSpec, or CSearchForFiles destructors is not executed.


Graham Pearson is a freelance writer, Windows platform consultant, and software developer. Graham can be reached at [email protected].


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.