This post will show how to import the FindFile utilities from kernel32 in order to improve application performance when you need to retrieve CreationTime, LastAccessTime or LastWriteTime information from a file.
My first attempt at a useful blog post…
I recently had a need to create a tree view in an ASP .Net app that would function similarly to a standard windows explorer. After tweaking permissions I was able to get the app to impersonate a new user that was granted read permissions onto the network share and got everything running smoothly…then came the dreaded phone call “It’s great, we just need a few changes before go live.”
The change seemed innocent enough, they just wanted a new column added into the tree view that would display the last modified time stamp for each file that was shown. A quick change to my GetFiles() loop to concatenate in the LastWriteTime property of the FileInfo object and I thought I was in business…not so much. The load time for the page went from sub 3 seconds to over 2 minutes. I tried everything I could think of: modifying when the nodes were populated, adjusting how they expanded, tweaking the buffer for the page, nothing seemed to work.
After some searching around online I found the cause of the problem…FileInfo sucks if you want anything other than the most basic info (read: Name) of files in a directory (for me this included CreationTime, LastAccessTime and LastWriteTime). The general consensus was to utilize the FindFirstFile and FindNextFile methods from the kernel32.dll. Once I located that info it wasn’t that hard to convert my code over to make use of the imports, but I ran across a couple “gotchas” working with it in VB (my company mandated programming language, don’t mock me) and thought I’d share the final code I have implemented for anyone else that may have a need for something like this.
First we define a couple Consts and Structs for use with the data we’ll be returning. This sets up the data to mesh with the FindFileData we’ll be working with from the dll imports.
Public Const MAX_PATH As Integer = 260
Public Const MAX_ALTERNATE As Integer = 14
Public Structure FILETIME
Public dwLowDateTime As UInteger
Public dwHighDateTime As UInteger
‘ The CharSet must match the CharSet of the corresponding DllImport signature
Public dwFileAttributes As UInteger
Public ftCreationTime As FILETIME
Public ftLastAccessTime As FILETIME
Public ftLastWriteTime As FILETIME
Public nFileSizeHigh As UInteger
Public nFileSizeLow As UInteger
Public dwReserved0 As UInteger
Public dwReserved1 As UInteger
Public cFileName As String
Public cAlternateFileName As String
An important item to note is that on the WIN32_FILE_DATA structure, the CharSet must be the same as the CharSet on the DllImports or you’re going to have all kinds of difficulties reading back the data.
Next are the actual imports. There are other methods that fit in with these for working with directory and file data, however I didn’t need / use them, so I’m not including them below.
Public Shared Function FindFirstFile(ByVal lpFileName As String, _
ByRef lpFindFileData As WIN32_FILE_DATA) As IntPtr
Public Shared Function FindNextFile(ByVal hFindFile As IntPtr, _
ByRef lpFindFileData As WIN32_FILE_DATA) As Boolean
Now with that bit of code out of the way, making use of it is fairly straight forward, but admittedly more complex than a simple GetFiles() loop. In the below code is the function I created to load in the file info and to create the tree node as a link out to the page that will actually “render” the file as well as a function that is used to simply “clean up” the display name of the file.
Function StripNulls(ByVal OriginalStr As String) As String
If (InStr(OriginalStr, vbNullChar) > 0) Then
OriginalStr = Left(OriginalStr, InStr(OriginalStr, vbNullChar) – 1)
StripNulls = OriginalStr
”’ The node to use as the parent for appending the file info
”’ The directory that contains all of the files for this tree node
Protected Sub AddFiles(ByRef rootNode As TreeNode, ByRef rootDirectory As DirectoryInfo)
Dim INVALID_HANDLE_VALUE As IntPtr = New IntPtr(-1)
Dim FileName As String
Dim hSearch As Long
Dim WFD As WIN32_FILE_DATA
hSearch = FindFirstFile(rootDirectory.FullName & “\*.pdf”, WFD)
If (hSearch <> INVALID_HANDLE_VALUE) Then
Do While (FindNextFile(hSearch, WFD))
FileName = StripNulls(WFD.cFileName)
‘This converts the Win32 File Data structures lastWriteTime to a human readable format
‘DateTime.FromFileTime(((CType(WFD.ftLastWriteTime.dwHighDateTime, Long) << 32) + WFD.ftLastWriteTime.dwLowDateTime)) rootNode.ChildNodes.Add(New TreeNode("” & FileName & “” & _
“” & DateTime.FromFileTime(((CType(WFD.ftLastWriteTime.dwHighDateTime, Long) << 32) + WFD.ftLastWriteTime.dwLowDateTime)) & _ "“, Nothing, “~/images/pdf.gif”, “~/showPDF.aspx?displayName=” & _
Server.UrlEncode(FileName) & “&fileName=” & Server.UrlEncode(rootDirectory.FullName & “\” & FileName), “_blank”))
Catch ex As Exception
I had initially left in the GetFiles() loop for grabbing the .Name of the file and then used the imported functions to insert the last modified time stamp, but that proved troublesome due to the order of the files not always matching up between the two iteration methods.
By incorporating this code in with my code that built out the directory tree nodes, I was able to provide the extra functionality the customer required and kept the load time at sub 3 seconds.