Walking Through Files
Sometime during your programming career, you’ve probably known the frustration of working with the Dir$ function. For simple operations, it’s very easy to use but it runs out of power quickly.
The worst limitation of Dir$ is documented right up front; you can’t call it recursively. This is a severe limitation for a function that ought to be able to
iterate through your entire hard disk, finding files that match a given description or displaying directory trees. Workarounds exist, but the performance is unacceptable as shown in the “Performance” sidebar below. The other problem with Dir$ is that it returns only the filename. If you’re building a file display with attributes, file length, and file time, you have to hit the disk once for each item for each file.
But nothing prevents you from calling the Win32 FindFirstFile, FindNextFile, and FindClose functions to iterate through files yourself. You can recurse as much as you like and grab all the file data you need in one fell swoop. That’s what Dir$ is doing under the surface, anyway—but it’s throwing away all the information it finds except the filename.
The following WalkAllFiles function walks recursively through all the files and directories from a given starting directory. It calls the UseFile method of the
Problem: Compare the speed of finding files with the Dir$ function to equivalent techniques, such as using FindFileFirst and using the operating system’s Find dialog box.
Problem |
Native code |
P-code |
Use the Visual Basic Dir$ function |
39 seconds |
39 seconds |
Use the Win32 FindFirstFile function |
9 seconds |
9 seconds |
Use the Windows Explorer Find dialog |
6 seconds |
|
Conclusion: The Dir$ function is clearly a loser, whereas FindFirstFile performs quite well. Your results will vary, depending on the host operating system, the size of the disk, and how many levels of nested directories it has. The times above were recorded under Windows 95 on a machine with a 1 GB hard disk. Try it for yourself with the Time It program. You might find a use for the function being tested (FindFiles in FILETOOL.BAS) in your own programs. It finds all occurences of a given file and returns the locations in a collection.
IUseFile interface to pass all the file data it finds back to the client. Here’s IUseFile:
Function UseFile(UserData As Variant, FilePath As String, _
FileInfo As CFileInfo) As Boolean
End Function
The first parameter is any user-defined integer that the caller wants to pass to the client. Since UserData is passed by reference, the client can modify the data and pass it back. The FilePath parameter might be useful to clients, especially during a recursive walk. The FileInfo parameter contains everything the client might want to know about the file.
Here’s the WalkAllFiles function that uses IUseFile:
Function WalkAllFiles(fileit As IUseFile, _
Optional ByVal ewmf As EWalkModeFile = ewmfBoth, _
Optional ByVal Start As String) As Boolean
‘ Statics for less memory use in recursive procedure
Static sName As String, fd As WIN32_FIND_DATA, iLevel As Long
Static fi As New CFileInfo
Dim hFiles As Long, f As Boolean
If Start = sEmpty Then Start = CurDir$
‘ Maintain level to ensure collection is cleared first time
If iLevel = 0 Then Start = MUtility.NormalizePath(Start)
iLevel = iLevel + 1
‘ Find first file (get handle to find)
hFiles = FindFirstFile(Start & “*.*”, fd)
f = (hFiles <> INVALID_HANDLE_VALUE)
Do While f
sName = MBytes.ByteZToStr(fd.cFileName)
‘ Skip . and ..
If Left$(sName, 1) <> “.” Then
‘ Create a file info object from file data
fi.CreateFromFile Start & sName, fd.dwFileAttributes, _
fd.nFileSizeLow, fd.ftLastWriteTime, _
fd.ftLastAccessTime, fd.ftCreationTime
If fd.dwFileAttributes And vbDirectory Then
If ewmf And ewmfDirs Then
‘ Let client use directory data
WalkAllFiles = fileit.UseFile(iLevel, Start, fi)
‘ If client returns True, walk terminates
If WalkAllFiles Then Exit Function
End If
‘ Call recursively on each directory
WalkAllFiles = WalkAllFiles(fileit, ewmf, _
Start & sName & “\”)
Else
If ewmf And ewmfFiles Then
‘ Let client use file data
WalkAllFiles = fileit.UseFile(iLevel, Start, fi)
‘ If client returns True, walk terminates
If WalkAllFiles Then Exit Function
End If
End If
End If
‘ Keep looping until no more files
f = FindNextFile(hFiles, fd)
Loop
f = FindClose(hFiles)
‘ Return the matching files in collection
iLevel = iLevel - 1
End Function
The client can use all or part of the data it receives in any way it wants. The WalkAllFiles function passes the recursion level in the UserData parameter to allow the client to format data according to its nesting level. A client that wants to use WalkAllFiles as a search engine can stop the walk by returning True from the UseFile method when it finds a file that meets its search criteria. In the Test Shell Folders application in Figure 11-11, the walk can be stopped by clicking the Stop button, which sets a flag telling the client to return True on the next iteration. You’ll soon get a chance to compare WalkAllFiles to the similar WalkAllFolders function, which walks through the shell name space.
I provide a similar WalkFiles function that iterates through a single directory. I’m not going to show the code, but here’s the signature:
Function WalkFiles(fileit As IUseFile, _
Optional ByVal ewmf As EWalkModeFile = ewmfBoth, _
Optional ByVal Start As String, _
Optional UserData As Variant) As Boolean
WalkFiles also calls the IUseFile interface to pass data to the client, but the UseFile parameters have a different purpose for a single-directory walk. The path doesn’t have much value in this case and will usually be ignored. The level wouldn’t be of much use either, so instead WalkFiles takes an optional UserData parameter that will be passed on to the client in the UserData parameter of UseFile. The client implementation can pass back modified data. For example, the Test Shell Folders program passes a count variable with an initial value of zero. The UseFile implementation increments this count for each file and then displays the count of files at the end of the walk.
Figure 11-11. The Test Shell Folders application.