|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||
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:
|
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). |
|
|
|
|||||
|
|||||
|
|||||
Copyright ©1996-2011 VBnet and Randy Birch. All Rights Reserved. |