The Win32 API functions FindFirstFile and FindNextFile can be used to obtain file information. In this tutorial we explore these functions and examine the WIN32_FIND_DATA structure in excruciating detail. During the exploration we will create a few simple functions and at the end, will construct a project that uses the FindFirstFile and FindNextFile API function.
This Tutorial is part of a series of tutorials for the Delphi programmer who is seeking additional information about the Win32 API.
|
Tutorial Objectives |
Tutorial Table of Contents |
|
In this tutorial we will explorer the FindFirstFile, FindNextFile and FindClose API functions. At the end of this tutorial you should:
|
Delphi Tutorial: Using
FindFirstFile and FindNextFile The TWin32FindData record
structure nFileSizeHigh and nFileSizeLow |
The function declaration for FindFirstFile can be obtained from the Microsoft Win32 Programmers Reference. It looks something like this:
|
HANDLE FindFirstFile( LPCTSTR lpFileName, // pointer to name of file to search for LPWIN32_FIND_DATA lpFindFileData // pointer to returned information ); |
The Object Pascal declaration for this function can be obtained from Windows.pas or translated from the C style code and looks something like this:
|
function FindFirstFile( lpFileName: PAnsiChar; var lpFindFileData: TWIN32FindDataA ): THandle; stdcall; |
In a Delphi program, we would use the function like this:
|
uses Windows.pas Function AFileSize(FileName : String) : Int64; var FileHandle : THandle; FindData : TWin32FindData; begin FileHandle := FindFirstFile(PChar(FileName), FindData); if FileHandle <> INVALID_HANDLE_VALUE then begin Windows.FindClose(FileHandle); Result := Int64(FindData.nFileSizeHigh) shl Int64(32) + Int64(FindData.nFileSizeLow); end else Result := 0; end; |
The TWin32FindData record structure in Object Pascal corresponds to the WIN32_FIND_DATA structure. It is declared in Windows.pas. Borland has not provided any information about the structure so we need to use both Windows.pas and the Microsoft Win32 Programmers Reference to determine what each field means.
|
_WIN32_FIND_DATAA = record dwFileAttributes: DWORD; // file attributes ftCreationTime: TFileTime; // Created date and time ftLastAccessTime: TFileTime; // Last accessed date and time ftLastWriteTime: TFileTime; // Modified date and time nFileSizeHigh: DWORD; // file size high word nFileSizeLow: DWORD; // file size low word dwReserved0: DWORD; // reserved for future use dwReserved1: DWORD; // reserved cFileName: array[0..MAX_PATH - 1] of AnsiChar; // full file name cAlternateFileName: array[0..13] of AnsiChar; // alternate (short) file name end; |
We will explore each of these fields and use them in a Delphi program.
dwFileAttributes contains the file attributes. Use the AND operator to combine dwFileAttributes with a specific attribute constant. If the result is greater than zero, the file has the attributes. For example, if (dwFileAttributes AND FILE_ATTRIBUTE_READONLY) > 0 then the file is a read-only file.
The complete set of constants that can be used with dwFileAttributes are defined in Windows.pas. They are:
|
Constant |
Description |
|
FILE_ATTRIBUTE_ARCHIVE |
The file is ready for archiving (backup). The file has been modified since it was last archived. |
|
FILE_ATTRIBUTE_COMPRESSED |
The file is compressed |
|
FILE_ATTRIBUTE_DIRECTORY |
This is a directory. |
|
FILE_ATTRIBUTE_HIDDEN |
The file is hidden. |
|
FILE_ATTRIBUTE_NORMAL |
The file has no other attributes set. |
|
FILE_ATTRIBUTE_OFFLINE |
The file data has been physically moved to offline storage. |
|
FILE_ATTRIBUTE_READONLY |
The file is read-only |
|
FILE_ATTRIBUTE_SYSTEM |
The file is part of the operating system. |
|
FILE_ATTRIBUTE_TEMPORARY |
This is a temporary file. |
The following example uses FindFirstFile and dwFileAttributes to determine if a file is a read-only file. A more complex example, which accepts the dwFileAttributes DWORD value as a parameter is included at the end of this tutorial.
|
// determine if the file specified in Edit1.Text is a read-only file procedure TForm1.Button3Click(Sender: TObject); var FileHandle : THandle; FindData : TWin32FindData; begin FileHandle := FindFirstFile(PChar(Edit1.Text), FindData); if FileHandle = INVALID_HANDLE_VALUE then Label2.Caption := 'File not Found' else if FindData.dwFileAttributes and FILE_ATTRIBUTE_READONLY > 0 then Label2.Caption := 'File is a readonly file' else Label2.Caption := 'File is not a readonly file'; Windows.FindClose(FileHandle); end; |
ftCreationTime is a TFileTime record structure that contains the date and time that the file was created in Coordinated Universal Time (UTC) format. The TFileTime structure corresponds to the Win32 FILETIME structure, which is defined as:
|
typedef struct _FILETIME { // ft DWORD dwLowDateTime; DWORD dwHighDateTime; } FILETIME; |
To convert ftCreationTime to an Object Pascal TDateTime format, use FileTimeToLocalFileTime, FileTimeToSystemTime and SystemTimeToDateTime. The first two functions are declared in Windows.pas and the last function is declared in SysUtils.
|
function FileTimeToLocalFileTime(const lpFileTime: TFileTime; var lpLocalFileTime: TFileTime): BOOL; stdcall; function FileTimeToSystemTime(const lpFileTime: TFileTime; var lpSystemTime: TSystemTime): BOOL; stdcall; function SystemTimeToDateTime(const SystemTime: TSystemTime): TDateTime; |
We can build our own function to convert TFileTime to TDateTime using the function outlined below. This function is explained in more detail in: Converting TFileTime to TDateTime.
|
// convert TFileTime to a TDateTime Function FileTimeToDateTime(FileTime : TFileTime) : TDateTime; var LocalTime : TFileTime; SystemTime : TSystemTime;
begin
Result := EncodeDate(1900,1,1); // set default return in case of failure;
if FileTimeToLocalFileTime(FileTime, LocalTime) then if FileTimeToSystemTime(LocalTime, SystemTime) then Result := SystemTimeToDateTime(SystemTime);
end; // function FileTimeToDateTime |
The function can be used to obtain the file creation date as a TDateTime object value.
|
CreateDate := FileTimeToDateTime(FindData.ftCreationTime); |
ftLastAccessTime is a TFileTime record structure that contains the date and time that the file was created in Coordinated Universal Time (UTC) format. We can use the FileTimeToDateTime function that we have already written to convert this to a TDateTime object value that contains a date that we can use in Delphi.
|
AccessTime := FileTimeToDateTime(FindData.ftLastAccessTime); |
ftLastWriteTime is a TFileTime record structure that contains the date and time that the file was last modified in Coordinated Universal Time (UTC) format. Our FileTimeToDateTime function can be used to convert this to a TDateTime object value.
|
WriteTime := FileTimeToDateTime(FindData.ftLastWriteTime); |
The file size in this structure is defined by nFileSizeHigh and nFileSizeLow. You can convert this to an Int64 value using the formula:
FileSize = nFileSizeHigh shl 32 + nFileSizeLow
To ensure the correct result, you must explicitly typecast all variables and values in the expression as Int64. This is explained in greater detail in Obtain the correct file size.
The following function converts these two DWORD values to an Int64 value that contains the correct file size, even for large files greater than 2 GB.
|
Function FileSizesToInt64(FileSizeHigh, FileSizeLow : DWord) : Int64; begin Result := Int64(FileSizeHigh) shl Int64(32) + Int64(FileSizeLow); end; |
cFileName contains the full (long) file name for the file. It is declared as
|
cFileName: array[0..MAX_PATH - 1] of AnsiChar; // full file name |
MAX_PATH is a constant declared in Windows.pas which holds the maximum size of a file path and file name. In Delphi 6 it is 260.
Delphi lets you treat arrays of AnsiChar as if they were Pascal style strings.
|
// a few ways to use cFileName // LongFileName : String; LongFileName := FindData.cFileName; // this just copies the array Label2.Caption := FindData.cFileName; // or you can assign it to a label Memo1.Lines.Add(FindData.cFileName); // or add it to a memo ShowMessage(FindData.cFileName); |
cAlternateFileName contains the alternate file name for the file. This is the file name used by the legacy DOS 8.2 file system. It can be used as if it were a Pascal style string and is declared as
|
cAlternateFileName: array[0..13] of AnsiChar; // alternate (short) file name |
In Delphi you use FindFirst to obtain the first instance of a file matching a specified search pattern. You use FindClose to release the memory allocated by FindFirst. Both function are included in Delphi’s SysUtils unit.
|
// The declarations for
FindFirst and FindClose in SysUtils function FindFirst(const Path: string; Attr: Integer; var F: TSearchRec): Integer; procedure FindClose(var F: TSearchRec); // The SysUtils version of FindClose |
When you use the Win32 API FindFirstFile function it must be followed by the Win32 API FindClose function, which closes the search handle. These functions are declared in Windows.pas.
|
// The declaration for FindFirstFile
and FindClose in Windows.pas function FindFirstFile(lpFileName: PAnsiChar; var lpFindFileData: TWIN32FindDataA): THandle; stdcall; procedure FindClose(var F: TSearchRec); // The Windows.pas (win32 API)version of FindClose |
You can identify which FindClose function you want to use by preceding it with the Unit.
|
SysUtils.FindClose(SearchRec); // the SysUtils function Windows.FindClose(FILEHANDLE); // the Win32 API function |
In Delphi you use FindFirst and FindNext to obtain a list of files. Finally, you use SysUtils.FindClose to release the memory allocated by FindFirst. You can also use the Win32 API functions FindFirstFile, FindNextFile and Windows.FindClose.
The API function declarations for FindNextFile is given below followed by the corresponding Object Pascal function declaration from Windows.pas
|
Legacy C |
|
BOOL FindNextFile( HANDLE hFindFile, // handle to search LPWIN32_FIND_DATA lpFindFileData // pointer to structure for data on found file ); |
|
Object Pascal |
|
function FindNextFile( hFindFile: THandle; var lpFindFileData: TWIN32FindData ): BOOL; stdcall; |
|
Example: |
|
// list all the files matching
a search argument (e.g. c:\windows\*.*) procedure TForm1.Button2Click(Sender: TObject); var FindData : TWin32FindData; FileHandle : THandle; begin FileHandle := FindFirstFile(PChar(Edit1.Text), FindData); if FileHandle
= INVALID_HANDLE_VALUE then // not found then exit; // exit
Memo1.Lines.Clear; // clear
the memo Memo1.Lines.Add(FindData.cFileName); // process the first file While FindNextFile(FileHandle, FindData) do Memo1.Lines.Add(FindData.cFileName); // process additional files Windows.FindClose(FileHandle); // close the File Handle end; |
· Create a new application by choosing New from Delphi’s File menu and choosing Application.
· Drop a Label component on the form and set the properties as follows
Autosize: True
Caption: File Mask
Left: 12
Name: Label1
Top: 20
· Drop an Edit (TEdit) on the form and set the properties as follows:
Left: 68
Name: Edit1
Text: C:\WINDOWS\*.*
Top: 16
Width: 325
· Drop a Button on the form and set the properties to:
Name: Button1
Left: 400
Top: 16
· Drop a ListView onto your form and set the properties to:
Height: 321
Left: 68
Top: 48
ViewStyle: vsReport
Width: 405
· Ensure that your Unit1 uses clause contains Windows (it should)
· Add the following code to the Form1 On Activate Event:
|
// initialize the column
headers for the ListView procedure TForm1.FormActivate(Sender: TObject); var ListColumn : TListColumn; begin With ListView1 do begin ListColumn := Columns.Add; ListColumn.Caption := 'File Name'; ListColumn.Width := 200; ListColumn := Columns.Add; ListColumn.Caption := 'Size'; ListColumn.Width := 125; ListColumn := Columns.Add; ListColumn.Caption := 'Attributes'; ListColumn.Width := 100; ListColumn := Columns.Add; ListColumn.Caption := 'Created'; ListColumn.Width := 125; ListColumn := Columns.Add; ListColumn.Caption := 'Accessed'; ListColumn.Width := 125; ListColumn := Columns.Add; ListColumn.Caption := 'Modified'; ListColumn.Width := 125; ListColumn := Columns.Add; ListColumn.Caption := 'Short File Name'; ListColumn.Width := 100; end; //
with ListView1 end; // TForm1.FormActivate |
|
procedure TForm1.Button1Click(Sender: TObject); var FindData : TWin32FindData; FileHandle : THandle; begin FileHandle := FindFirstFile(PChar(Edit1.Text), FindData); if FileHandle = INVALID_HANDLE_VALUE then exit; ProcessFoundFile(FindData); // Process the first found file While FindNextFile(FileHandle, FindData) do ProcessFoundFile(FindData); Windows.FindClose(FileHandle); // Close the file handle end; |
|
private { Private declarations } Procedure ProcessFoundFile(FindData : TWin32FindData); Function AttrToStr(FileAttr : DWORD) : String; Function FileSizesToInt64(FileSizeHigh, FileSizeLow : DWord) : Int64; Function FileTimeToDateTime(FileTime : TFileTime) : TDateTime; public { Public declarations } end; |
|
// Process one found file Procedure TForm1.ProcessFoundFile(FindData : TWin32FindData); var ListItem : TListItem; MyDateFormat : String; begin MyDateFormat := ShortDateFormat + ' ' + ShortTimeFormat; // format used in FormatDateTime; With FindData Do Begin ListItem
:= ListView1.Items.Add; // Add a
new line to the ListView ListItem.Caption := cFileName; // long file name ListItem.SubItems.Add(FormatFloat('#,##0 bytes', FileSizesToInt64(nFileSizeHigh, nFileSizeLow))); // file size ListItem.SubItems.Add(AttrToStr(dwFileAttributes)); // file attributes ListItem.SubItems.Add(FormatDateTime(MyDateFormat, // date and time created FileTimeToDateTime(FindData.ftCreationTime)));
ListItem.SubItems.Add(FormatDateTime(MyDateFormat, // last
accessed FileTimeToDateTime(FindData.ftLastAccessTime))); ListItem.SubItems.Add(FormatDateTime(MyDateFormat, // last modified FileTimeToDateTime(FindData.ftLastWriteTime)));
ListItem.SubItems.Add(FindData.cAlternateFileName); // short
file name end; // with FindData end; // function ProcessFoundFile |
|
|
|
// Convert the dwFileAttributes
DWORD to a string of letters indicating the attributes Function TForm1.AttrToStr(FileAttr : DWORD) : String; begin Result := ''; // initialize the result if (FileAttr AND FILE_ATTRIBUTE_ARCHIVE)> 0 then Result := Result + 'A'; if (FileAttr AND FILE_ATTRIBUTE_COMPRESSED)> 0 then Result := Result + 'C'; if (FileAttr AND FILE_ATTRIBUTE_DIRECTORY)> 0 then Result := Result + 'D'; if (FileAttr AND FILE_ATTRIBUTE_HIDDEN)> 0 then Result := Result + 'H'; if (FileAttr AND FILE_ATTRIBUTE_NORMAL)> 0 then Result := Result + 'N'; if (FileAttr AND FILE_ATTRIBUTE_OFFLINE)> 0 then Result := Result + 'O'; if (FileAttr AND FILE_ATTRIBUTE_READONLY)> 0 then Result := Result + 'R'; if (FileAttr AND FILE_ATTRIBUTE_SYSTEM)> 0 then Result := Result + 'S'; if (FileAttr AND FILE_ATTRIBUTE_TEMPORARY)> 0 then Result := Result + 'T'; end; // function FileAttr |
|
|
|
// Convert TFileTime to a
TDateTime Function TForm1.FileTimeToDateTime(FileTime : TFileTime) : TDateTime; var LocalTime : TFileTime; SystemTime : TSystemTime; begin Result := EncodeDate(1900,1,1); // set default return in case of failure; if FileTimeToLocalFileTime(FileTime, LocalTime) then if FileTimeToSystemTime(LocalTime, SystemTime) then Result := SystemTimeToDateTime(SystemTime); end; // function FileTimeToDataTime |
|
|
|
// Convert the FileSizeHigh
and FileSizeLow DWORD values to a file size Function TForm1.FileSizesToInt64(FileSizeHigh, FileSizeLow : DWord) : Int64; begin Result := Int64(FileSizeHigh) shl Int64(32) + Int64(FileSizeLow); end; |
Run your program, the result should look something like:

The Win32 FindFirstFile API function returns the TWin43FindData record structure. The structure contains the following fields:
TWin32FindData.dwFileAttributes: DWORD // the file attributes as a DWORD
TWin32FindData.ftCreationTime // creation data and time is a UTC time
TWin32FindData.ftLastAccessTime // last access time as a UTC time
TWin32FindData.ftLastWriteTime // modified time as a UTC time
TWin32FindData.nFileSizeHigh: DWORD; // file size high word
TWin32FindData.nFileSizeLow: DWORD; // file size low word
TWin32FindData.dwReserved0: DWORD; // reserved for future use
TWin32FindData.dwReserved1: DWORD; // reserved
TWin32FindData.cFileName: array[0..MAX_PATH - 1] of AnsiChar; // full file name
TWin32FindData.cAlternateFileName: array[0..13] of AnsiChar; // alternate (short) file name
FindNextFile is used to continue searching for additional files matching the search criteria.
File attributes can be obtained by combining dwFileAttributes with a constant using AND.
The creation time, last accessed time and modified time can be obtained by converting them to a local-file-time, then to a TDateTime value.
The true file size is available using: Size := Int64(FindData.nFileSizeHigh) shl Int64(32) + Int64(FindData.nFileSizeLow);
cFileName is the actual (long) file name. cAlternateFileName is the short file name. Both can be treated as strings.
The handle must be release using the Windows.FindClose API function.
|
Delphi 6 online help |
|
|
WIN32.HLP |
|
|
|
Borland Delphi Run-time Library - Win32 API Interface Unit. |
|
Borland Delphi Run-time Library - Win32 API Shell objects Interface Unit. |
|
|
Win32 common controls interface unit |
|
|
Other tutorials in this series |
|
|
An excellent cooperative effort among Delphi programmers with lots of free source code |
|
The MER System Database Search |
A free newsgroup search engine you can use to find Delphi newsgroup postings |
|
|
The Microsoft support site for developers |
|
|
Borland’s support site. |
|
Newsgroups |
Borland Delphi newsgroups. |
The Tomes of Delphi: Win32 Core API - Windows 2000 Edition by John Ayers, ISBN 1-55622-750-7 Wordware Publishing Inc
The Tomes of Delphi: Win32 Shell API – Windows 2000 Edition by John Ayers, ISBN 1-55622-749-3 Wordware Publishing Inc
Windows 2000 SYSTEMS PROGRAMMING Black Book by Al Williams, 2000, ISBN 1-57610-280-7, The Coriolis Group
The source code contained in this tutorial is provided free of charge,
AS IS WITHOUT WARRANTY OF ANY KIND AND IS PROVIDED WITHOUT ANY IMPLIED WARRANTY
OF FITNESS FOR PURPOSE OR MERCHANTABILITY. You may incorporate this source code
into your own program but you do so at your own risk. Please remember to test
your program and any source code copied from this site. Please report errors
and improvements to jpgriffiths.com.
Copyright © 2003 Paul Griffiths, www.jpgriffiths.com.
Last Updated: [January 2003]