Delphi Tutorial: Using FindFirstFile and FindNextFile

 

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:

 

  • Know how to use FindFirstFile
  • Understand the TWin32FindData record structure
  • Understand TWin32FindData.dwFileAttributes
  • Understand TWin32FindData.ftCreationTime
  • Understand TWin32FindData.ftLastAccessTime
  • Understand TWin32FindData.ftLastWriteTime
  • Know how to convert TFileTime to TDateTime
  • Understand FindClose
  • Understand FindNextFile
  • Construct a simple file list viewer

 

Delphi Tutorial: Using FindFirstFile and FindNextFile. 1

FindFirstFile API function. 1

The TWin32FindData record structure. 2

dwFileAttributes : DWORD; 2

ftCreationTime : TFileTime; 3

ftLastAccessTime : TFileTime; 4

ftLastWriteTime : TFileTime; 4

nFileSizeHigh and nFileSizeLow.. 4

cFileName. 5

cAlternateFileName. 5

FindClose. 5

Using FindNextFile. 6

Putting it all together, a simple file list program. 7

Summary. 10

References. 11

References on your computer. 11

Internet 11

Books. 11

Misc. 11

Use of Source Code. 11

Copyright 11

Last Update. 11

 

 

FindFirstFile API function

 

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

 

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 : DWORD;

 

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 : TFileTime;

 

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 : TFileTime;

 

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 : TFileTime;

 

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);

 

 

 

nFileSizeHigh and nFileSizeLow

 

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

 

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

 

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

 

 

 

 

FindClose

 

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

 

 

 

Using FindNextFile

 

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;

 

Putting it all together, a simple file list program.

 

·        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:

 

 

 

Summary

 

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.

 

 

 

References

 

References on your computer.

 

Delphi 6 online help

 

 

WIN32.HLP

 

The Microsoft Win32 Programmers Reference

Windows.pas

 

Borland Delphi Run-time Library - Win32 API Interface Unit.  

ShlObj.pas

Borland Delphi Run-time Library - Win32 API Shell objects Interface Unit.

 

CommCtrl.pas

Win32 common controls interface unit

 

 

Internet

 

Other tutorials

 

Other tutorials in this series

The Jedi Code Library

 

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

Microsoft Developers Network

 

The Microsoft support site for developers

Borland Developers Network

 

Borland’s support site.

Newsgroups

Borland Delphi newsgroups.

 

 

Books

 

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

 

Misc.

Use of Source Code

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

Copyright © 2003 Paul Griffiths,  www.jpgriffiths.com.

Last Update

Last Updated:  [January 2003]