Visual Basic File API Routines

Minimal Code for a Recursive Search for Folders (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.

VBnet already contains several pages that show how to use the FindFirstFile and FindNextFile APIs to rapidly search for files or folders.  Yet several programmers have asked for pages that provide just the most minimal code to retrieve either file or folder info, across single or multiple drives.  This series of four pages address these requests.

Certainly the easiest way to pass multiple search parameters to search routines is via a customized User-Defined Type, and this demo, like the other FindFirstFile demos, uses a UDT to allow for extending the capabilities of a routine by simply adding a new member to the UDT.

This page shows the minimalist code for searching a singe drive for all folders under the specified start path. Searching for folders requires just search routine, with a couple of support methods to parse the returned strings once it has been determined that a folder has been located.

The routine is extremely fast, as the illustration shows, and for a non-recursive search is just a bit faster than a corresponding Dir() method. However, the routine really shines in a recursive search as it is considerably faster than a recursive Dir() method. Compared to the FindFirstFile methods , the FileSystemObject is, well, no comparison.  Create your own demo to see the huge performance hit that the collection in the FSO imparts on your search or see the comparison demo in Related above.

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.

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.
  • The loading of a list box, even when its visibility has been set to false, still imparts a performance hit on the routines which increases as the total size of the items added (not the number of items added) starts to exceeds 32k.  In doing a search of my C:\ drive for just folders when populating the list with the results, the elapsed time was 1.73 seconds (shown above). By commenting out the List1.Additem call in SearchForFolders reduced the elapsed time to 1.47 seconds.  But ... and this is the big but ... performing the same searches against C:\ using the file search code at  returned in a whopping 46.22 seconds when adding 58, 300 items to the list box.  But, with the  List1.Additem call in GetFileInformation commented out, the routine performing the same search in a mere 2.64 seconds. The point here is that any use of an interface to display the results will impact performance to some degree. If you only need to locate files across a drive without utilizing an interface the methods are amazingly fast. Performing any action that requires constant (re)allocation of string resources will impede performance, possibly seriously.
  • This code was developed and run on Windows XP Pro. Under Win9x, where the number of entries in a list box 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
   
  'clear results textbox and list
   Text3.Text = ""
   
  'set up search params
   With FP
      .sFileRoot = Text1.Text       'start path
      .sFileNameExt = Text2.Text    'file type of interest
      .bRecurse = Check1.Value = 1  '1 = do recursive search
   End With

  'setting the list visibility to false
  'increases clear and load time
   List1.Visible = False
   List1.Clear
   
  'get start time, folders, and finish time
   tstart = GetTickCount()
   Call SearchForFolders(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 SearchForFolders(FP As FILE_PARAMS)

   Dim WFD As WIN32_FIND_DATA
   Dim hFile As Long
   Dim sRoot As String
   Dim spath As String
   Dim sTmp As String
   
   sRoot = QualifyPath(FP.sFileRoot)
   spath = sRoot & FP.sFileNameExt
   
  'obtain handle to the first match
   hFile = FindFirstFile(spath, WFD)
   
  'if valid ...
   If hFile <> INVALID_HANDLE_VALUE Then
         
      Do
         
        'Only folders are wanted, so discard files
        'or parent/root DOS folders.
         If (WFD.dwFileAttributes And FILE_ATTRIBUTE_DIRECTORY) And _
             Asc(WFD.cFileName) <> vbDot Then
            
           'must be a folder, so remove trailing nulls
            sTmp = TrimNull(WFD.cFileName)
                       
           'This is where you add code to store
           'or display the returned file listing.
           '
           'if you want the folder name only, save 'sTmp'.
           'if you want the full path, save 'sRoot & sTmp'
            FP.Count = FP.Count + 1
            List1.AddItem sRoot & sTmp
            
           'if a recursive search was selected, call
           'this method again with a modified root
            If FP.bRecurse Then
            
               FP.sFileRoot = sRoot & sTmp
               Call SearchForFolders(FP)
            
            End If

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


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


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
 Comments
Before running, assure that any hard-coded paths reflect accurate paths on your system.

It must also be noted that, 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 contains 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