Visual Basic File API Routines
FindFirstFile: Performance Comparison - FSO vs. API
Posted:   Sunday October 9, 1999
Updated:   Monday December 26, 2011
Applies to:   VB4-32, VB5, VB6
Developed with:   VB6, Windows NT4
OS restrictions:   None
Author:   VBnet - Randy Birch


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
VB6 or VB5 with the FileSystemObject.

Whenever someone in the newsgroups ask about performing file searches in a specific folder, you can count on three different answers - use Dir(), use the FileSystemObject (FSO), and use the FindFirstFile APIs.

I always recommend the latter ... and then put up with comments about the amount of code required to actually do it compared to the Dir or FSO method. The FSO method looks so compact ... but what is the real price paid for using an unnecessary COM object? The illustration shows the results on my system for a non-recursive ListView listing of all files in my \winnt\system32\ folder.  The demo is as simple as possible to reduce unneeded overhead, and both methods return identical data.

The FileSystemObject method uses the FSO and one additional routine to populate the ListView. The API method uses FindFirstFile/FindNextFile/FindClose to perform the search, along with SHGetFileInfo for obtaining the file type and FileTimeToSystemTime for obtaining the file date. In both methods the elapsed time is tracked using the GetTickCount API.

The first illustration shows the comparative results for the non-recursive search for files in my \winnt\system32\ folder, populating the ListView with name, size, date, and file-type info. The second illustrations shows the result of the same searches, but with the code to populate the SubItem code commented out, eliminating some of the ListView overhead as well as API and FSO property calls. The API methods have been tweaked with suggestions provided by Brad Martinez, providing even better performance compared to the FSO.

Regardless of the amount of data displayed, tests prove that a well-written API method blows the doors off of a similar FileSystemObject method. Sure it involves more code, but the end results should be obvious to anyone wishing to maximize the performance of their application.

 BAS Module Code

 Form Code
Ad to a form a ListView (ListView1), two command buttons (Command1, Command2) and four textboxes (Text1/Text2 for Command 1, and Text3 and Tex4 forCommand2). Add the following code to the form:

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 MAX_PATH As Long = 260
Private Const INVALID_HANDLE_VALUE As Long = -1
Private Const SHGFI_TYPENAME As Long = &H400

   hIcon As Long
   iIcon As Long
   dwAttributes As Long
   szDisplayName As String * MAX_PATH
   szTypeName As String * 80
End Type

   wYear As Integer
   wMonth As Integer
   wDayOfWeek As Integer
   wDay As Integer
   wHour As Integer
   wMinute As Integer
   wSecond As Integer
   wMilliseconds As Integer
End Type

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
   fAttributes As Long
   sFileRoot As String
   sFileNameExt As String
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 FileTimeToLocalFileTime Lib "kernel32" _
  (lpFileTime As FILETIME, _
   lpLocalFileTime As FILETIME) As Long

Private Declare Function FileTimeToSystemTime Lib "kernel32" _
  (lpFileTime As FILETIME, _
   lpSystemTime As SYSTEMTIME) As Long
Private Declare Function SystemTimeToVariantTime Lib "oleaut32" _
  (psystime As SYSTEMTIME, _
   pvtime As Double) As Long
Private Declare Function StrFormatByteSizeW Lib "shlwapi" _
  (ByVal qdwLow As Long, _
   ByVal qdwHigh As Long, _
   pwszBuf As Any, _
   ByVal cchBuf As Long) As Long

Private Declare Function SHGetFileInfo Lib "shell32" _
   Alias "SHGetFileInfoA" _
  (ByVal pszPath As String, _
   ByVal dwFileAttributes As Long, _
   psfi As SHFILEINFO, _
   ByVal cbSizeFileInfo As Long, _
   ByVal uFlags As Long) As Long

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

Private Sub Form_Load()

   Command1.Caption = "API - Non-recursive"
   Command2.Caption = "FileSystemObject"
   With ListView1
      .ColumnHeaders.Add , , "Filename"
      .ColumnHeaders.Add , , "Size"
      .ColumnHeaders.Add , , "Date"
      .ColumnHeaders.Add , , "Type"
      .View = lvwReport
      .Sorted = False
   End With
End Sub

Private Sub Command1_Click()

   Dim tstart As Double
   Dim tend As Double
   Screen.MousePointer = vbHourglass
   tstart = GetTickCount()
      With FP
         .sFileRoot = "c:\winnt\system32"
         .sFileNameExt = "*.*"
      End With
      Call GetFileInformation(FP)
   tend = GetTickCount()
   Screen.MousePointer = vbNormal
   Text1.Text = ListView1.ListItems.Count & " files found"
   Text2.Text = (tend - tstart) / 1000 & " seconds"

End Sub

Private Sub Command2_Click()

   Dim tstart As Single
   Dim tend As Single
   Dim fso As FileSystemObject
   Dim path As String
   Set fso = New FileSystemObject
   Screen.MousePointer = vbHourglass
   tstart = GetTickCount()

      AddFiles fso.GetFolder("c:\winnt\system32")
   tend = GetTickCount()
   Screen.MousePointer = vbNormal
   Text3.Text = ListView1.ListItems.Count & " files found"
   Text4.Text = (tend - tstart) / 1000 & " seconds"

End Sub

Private Function GetFileType(sFileName As String) As String

   Dim shinfo As SHFILEINFO
   Call SHGetFileInfo(sFileName, 0&, shinfo, Len(shinfo), SHGFI_TYPENAME)
   GetFileType = shinfo.szTypeName

End Function

Private Function GetFileDate(CT As FILETIME) As String

  If FileTimeToSystemTime(CT, ST) Then
     GetFileDate = Format$(DateSerial(ST.wYear, ST.wMonth, ST.wDay), "Short Date")
     GetFileDate = ""
  End If

End Function

Private Sub AddFiles(fn As Folder)

   Dim fi As File
   Dim itmX As ListItem
   Dim lv As ListView
   Set lv = ListView1

   With lv
      For Each fi In fn.Files

         Set itmX = .ListItems.Add(, , fi.Name)
         itmX.SubItems(1) = fi.Size
         itmX.SubItems(2) = fi.DateLastModified
         itmX.SubItems(3) = fi.Type
      Next fi
   End With
End Sub

Private Function FileTimeToVBDate(ftFile As FILETIME) As Date

  Dim ftLocal As FILETIME
  Dim dbl As Double

  Call FileTimeToLocalFileTime(ftFile, ftLocal)
  Call FileTimeToSystemTime(ftLocal, st)
  Call SystemTimeToVariantTime(st, dbl)
  FileTimeToVBDate = dbl

End Function

Private Sub GetFileInformation(FP As FILE_PARAMS)

   Dim hFile As Long
   Dim sPath As String
   Dim sRoot As String
   Dim itmX As ListItem
   Dim lv As ListView
   Dim sSize As String
   Set lv = ListView1
   sRoot = QualifyPath(FP.sFileRoot)
   sPath = sRoot & FP.sFileNameExt
   hFile = FindFirstFile(sPath, WFD)
      With lv
            If (WFD.dwFileAttributes And vbDirectory) = 0 Then
               Set itmX = .ListItems.Add(, , WFD.cFileName)
               sSize = Space$(30)
               Call StrFormatByteSizeW(WFD.nFileSizeLow, _
                                       WFD.nFileSizeHigh, _
                                       ByVal StrPtr(sSize), 30)
               itmX.SubItems(1) = sSize
               itmX.SubItems(2) = FileTimeToVBDate(WFD.ftLastWriteTime)
               itmX.SubItems(3) = GetFileType(sRoot & WFD.cFileName)
            End If
         Loop While FindNextFile(hFile, WFD)
      End With
      hFile = FindClose(hFile)
   End If

End Sub

Private Function QualifyPath(sPath As String) As String

   If Right$(sPath, 1) <> "\" Then
      QualifyPath = sPath & "\"
      QualifyPath = sPath
   End If
End Function
Before running, assure that you specify a valid path in the Command1 and Command2 click events.


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