Visual Basic File API Routines

Minimal Code for a Recursive Search for Files (single drive)
     
Posted:   Monday November 05, 2001
Updated:   Monday December 26, 2011
     
Applies to:   VB4-32, VB5, VB6
Developed with:   VB6, Windows XP Pro
OS restrictions:   None
Author:   VBnet - Randy Birch
     

Related:  

FindFirstFile: Recursive File Search for Single or Multiple File Types (minimal code)
FindFirstFile: Recursive File Search Including/Excluding Single or Multiple File Types (minimal code)
FindFirstFile: Recursive Search for Folders Using a Folder Mask (minimal code)
FindFirstFile: Recursive File Search (minimal code)
FindFirstFile: Recursive Search for Folders (minimal code)
FindFirstFile: Changing File and/or Folder Attributes Recursively
FindFirstFile: Fast Directory File Count
FindFirstFile: Extract Filename from a Full Path
FindFirstFile: Performance Comparison - FSO vs. API
FindFirstFile: Comparison of FindFirstFile and SearchTreeForFile
FindFirstFile: Save a Recursive Search of All Drives to Disk
FindFirstFile: Save a Recursive Search of Specified Drives to Disk
GetFileVersionInfo: File Search and File Property Info
GetLogicalDriveStrings: An API 'DriveExists' Routine
FindFirstFile: An API 'FileExists' Routine
FindFirstFile: An API 'FolderExists' Routine
     
 Prerequisites
None.

Here is the least code required to use the FindFirstFile and FindNextFile APIs to retrieve file info in either a specified folder, recursively under the specified folder, or across a single drive.

As used in the other search demos, a customized User-Defined Type is used as the mechanism to pass multiple search parameters to the search routines. This allows for extending the capabilities of a routine by simply adding a new member to the UDT and coding for it.

This page shows the minimalist code for searching a singe drive for all files under the specified start path. Searching for files requires a routine to locate each folder, and a separate routine to locate each file in it. Support methods are provided to parse the strings and qualify the path.

The default mode coded is to search only the specified folder. When the "Recurse" button is checked, the specified folder, and all subfolders under it, are searched.  When a drive alone is specified as the source path, the recursion searches all folders on the drive.

If you watch the illustration you may be surprised to see the 40+ search time for C:\.  Yet this routine is extremely fast, and for a non-recursive search is just a bit faster than a corresponding Dir() method. And compared to the FindFirstFile methods the FileSystemObject is, well, no comparison. 

The cause of the extremely long search time shown in illustration three can be easily explained and directly attributed to the capabilities (or more accurately the limitations) of the listbox. Even when the listbox visibility has been set to false, which speeds up adding files significantly, the loading of the listbox still imparts a performance hit which increases exponentially as the total size of the items added (in bytes, not the number of items) starts to exceed 32k. The first illustration (searching D:\vb) shows a typical search locating over 7300 files in 629 folders using the routine below. The second illustration shows the results of retrieving over 53, 000 files from 3,593 folders across all of drive C, but with the List1.Additem line in GetFileInformation commented out. The third illustration is the exact same call with the List1.Additem line left in, with the routine crawling in at an overall elapsed time of over 40 seconds. Any use of an interface to display results will impact performance to some degree, so if you only need to locate files across a drive (without the need of an interface) the methods are amazingly fast. Performing any action that requires constant reallocation of string resources impedes performance, possibly seriously as the sting size grows. And note too that this code was run on Windows XP pro. Under Win9x, there is simply no way you can list over 32k items.

Other Notes:

  • To search for multiple file types (ie a search for all *.frm;*.bas files) by specifying such a pattern as the extension of interest, see FindFirstFile: Recursive File Search for Single or Multiple File Types (minimal code) and FindFirstFile: Recursive File Search Including/Excluding Single or Multiple File Types (minimal code)
  • The demo uses the VB6 FormatNumber function which is not available in VB4-32 or VB5. Users of versions prior to VB6 can use the API methods shown  without any problems, but should change all FormatNumber references to Format$ in the code below.
  • Any interface to display results (such as the listbox shown) will impact the performance of the routine. If you only need to locate files across a drive - no need to list the files as in this demo - the routine here is amazingly fast. Any action that requires constant (re)allocation of string resources will impede application performance, possibly seriously, as the string size grows. And I recommend avoiding *.* searches when adding the files to a list or other interface component.

  • This code was developed and run on Windows XP Pro. Under Win9x, where the number of entries in a listbox is restricted, there is simply no way to list over 32k items.

 BAS Module Code
None.

 Form Code
Create a new project with a form containing four text boxes (Text1, Text2, Text3, Text4), a check boxes (Check1), a list box (List1) and a command button (Command1). Label as desired and add the following code:

Option Explicit
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Copyright ©1996-2011 VBnet/Randy Birch, All Rights Reserved.
' Some pages may also contain other copyrights by the author.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' Distribution: You can freely use this code in your own
'               applications, but you may not reproduce 
'               or publish this code on any web site,
'               online service, or distribute as source 
'               on any media without express permission.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Private Const vbDot = 46
Private Const MAXDWORD As Long = &HFFFFFFFF
Private Const MAX_PATH As Long = 260
Private Const INVALID_HANDLE_VALUE = -1
Private Const FILE_ATTRIBUTE_DIRECTORY = &H10

Private Type FILETIME
   dwLowDateTime As Long
   dwHighDateTime As Long
End Type

Private Type WIN32_FIND_DATA
   dwFileAttributes As Long
   ftCreationTime As FILETIME
   ftLastAccessTime As FILETIME
   ftLastWriteTime As FILETIME
   nFileSizeHigh As Long
   nFileSizeLow As Long
   dwReserved0 As Long
   dwReserved1 As Long
   cFileName As String * MAX_PATH
   cAlternate As String * 14
End Type

Private Type FILE_PARAMS
   bRecurse As Boolean
   sFileRoot As String
   sFileNameExt As String
   sResult As String
   sMatches As String
   Count As Long
End Type

Private Declare Function FindClose Lib "kernel32" _
  (ByVal hFindFile As Long) As Long
   
Private Declare Function FindFirstFile Lib "kernel32" _
   Alias "FindFirstFileA" _
  (ByVal lpFileName As String, _
   lpFindFileData As WIN32_FIND_DATA) As Long
   
Private Declare Function FindNextFile Lib "kernel32" _
   Alias "FindNextFileA" _
  (ByVal hFindFile As Long, _
   lpFindFileData As WIN32_FIND_DATA) As Long

Private Declare Function GetTickCount Lib "kernel32" () As Long



Private Sub Command1_Click()

   Dim FP As FILE_PARAMS  'holds search parameters
   Dim tstart As Single   'timer var for this routine only
   Dim tend As Single     'timer var for this routine only
   
  'setting the list visibility to false
  'increases the load time
   Text3.Text = ""
   List1.Clear
   List1.Visible = False
   
  'set up search params
   With FP
      .sFileRoot = Text1.Text       'start path
      .sFileNameExt = Text2.Text    'file type of interest
      .bRecurse = Check1.Value = 1  '1 = recursive search
   End With
   
  'get start time, get files, and get finish time
   tstart = GetTickCount()
   Call SearchForFiles(FP)
   tend = GetTickCount()
   
   List1.Visible = True
   
  'show the results
   Text3.Text = Format$(FP.Count, "###,###,###,##0") & _
                        " found (" & _
                        FP.sFileNameExt & ")"
                   
   Text4.Text = FormatNumber((tend - tstart) / 1000, 2) & "  seconds"
                                    
End Sub


Private Sub GetFileInformation(FP As FILE_PARAMS)

  'local working variables
   Dim WFD As WIN32_FIND_DATA
   Dim hFile As Long
   Dim sPath As String
   Dim sRoot As String
   Dim sTmp As String
      
  'FP.sFileRoot contains the path to search.
  'FP.sFileNameExt contains the full path and filespec.
   sRoot = QualifyPath(FP.sFileRoot)
   sPath = sRoot & FP.sFileNameExt
   
  'obtain handle to the first filespec match
   hFile = FindFirstFile(sPath, WFD)
   
  'if valid ...
   If hFile <> INVALID_HANDLE_VALUE Then

      Do
         
        'Even though this routine uses file specs,
        '*.* is still valid and will cause the search
        'to return folders as well as files, so a
        'check against folders is still required.
         If Not (WFD.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) = _
                 FILE_ATTRIBUTE_DIRECTORY Then

           'this is where you add code to store
           'or display the returned file listing.
           '
           'if you want the file name only, save 'sTmp'.
           'if you want the full path, save 'sRoot & sTmp'

           'remove trailing nulls
            FP.Count = FP.Count + 1
            sTmp = TrimNull(WFD.cFileName)
            List1.AddItem sRoot & sTmp

         End If
         
      Loop While FindNextFile(hFile, WFD)
      
      
     'close the handle
      hFile = FindClose(hFile)
   
   End If

End Sub


Private Sub SearchForFiles(FP As FILE_PARAMS)

  'local working variables
   Dim WFD As WIN32_FIND_DATA
   Dim hFile As Long
   Dim sPath As String
   Dim sRoot As String
   Dim sTmp As String
      
   sRoot = QualifyPath(FP.sFileRoot)
   sPath = sRoot & "*.*"
   
  'obtain handle to the first match
   hFile = FindFirstFile(sPath, WFD)
   
  'if valid ...
   If hFile <> INVALID_HANDLE_VALUE Then
   
     'This is where the method obtains the file
     'list and data for the folder passed.
      Call GetFileInformation(FP)

      Do
      
        'if the returned item is a folder...
         If (WFD.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) Then
            
           '..and the Recurse flag was specified
            If FP.bRecurse Then
            
              'and if the folder is not the default
              'self and parent folders (a . or ..)
               If Asc(WFD.cFileName) <> vbDot Then
               
                 '..then the item is a real folder, which
                 'may contain other sub folders, so assign
                 'the new folder name to FP.sFileRoot and
                 'recursively call this function again with
                 'the amended information.

                 'remove trailing nulls
                  FP.sFileRoot = sRoot & TrimNull(WFD.cFileName)
                  Call SearchForFiles(FP)
                  
               End If
               
            End If
            
         End If
         
     'continue looping until FindNextFile returns
     '0 (no more matches)
      Loop While FindNextFile(hFile, WFD)
      
     'close the find handle
      hFile = FindClose(hFile)
   
   End If
   
End Sub


Private Function QualifyPath(sPath As String) As String

  'assures that a passed path ends in a slash
   If Right$(sPath, 1) <> "\" Then
      QualifyPath = sPath & "\"
   Else
      QualifyPath = sPath
   End If
      
End Function


Private Function TrimNull(startstr As String) As String

  'returns the string up to the first
  'null, if present, or the passed string
   Dim pos As Integer
   
   pos = InStr(startstr, Chr$(0))
   
   If pos Then
      TrimNull = Left$(startstr, pos - 1)
      Exit Function
   End If
  
   TrimNull = startstr
  
End Function
 Comments
Before running, assure that any hard-coded paths reflect accurate paths on your system.

Since this example uses the listbox to return the results, on systems containing many files you may, eventually, hit the listbox item limit of 32k items under an non-NT-based system. While there is no practical or reliable way to extend the number of items a listbox can contain on a Win9x system (without resorting to an owner-drawn control), you can increase the number of files read (if exceeding the size of a Long) by declaring the appropriate variables as Currency instead.

Note: While it may be convenient to utilize VB's built-in constants in place of the FILE_ATTRIBUTE_* API values, care must be taken. There is a difference between related constant values that may cause unexpected performance at some point. For example, the constant 'vbNormal' is defined as having a value of 0, whereas the API FILE_ATTRIBUTE_NORMAL has a value of &H80 (decimal 128).


 
 

PayPal Link
Make payments with PayPal - it's fast, free and secure!

 
 
 
 

Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved.
Terms of Use  |  Your Privacy

 

Hit Counter