Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations TouchToneTommy on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

FindFirst/FindNext

Status
Not open for further replies.

Glenn9999

Programmer
Jun 19, 2004
2,311
US
I'll start out by saying that I'm well aware of how FindFirst/FindNext work, so that's really not an issue. The question I did have was this: How does everyone generally deal with the (what would seem to be) inconsistent output that these functions generate? For example, with a initial call of

Code:
FindFirst('C:\*.*', faDirectory, sr);

I get 7 of the 9 directories I expected back. The other 2 not returned had "Hidden" attributes on them - I would expect all directories to be returned, logically, with this call. But to go on, I get 4 files with only the archive flag set on them.

I can't say if I know if the behavior of this call has changed over time (it's been this way since Turbo Pascal), but it doesn't seem to make any logical sense. Personally, I just use faAnyFile in the second parm and post-filter the files myself for whatever attributes I'm wanting in the return output. But that seems like duplicated work that logically should be handled by this routine...

So what do you all do to handle this? Or has this call been changed to actually work in a logical way?
 
I also use the faAnyFile and post-filter. But I think in theory it should work by combining filters in the FindFirst call, eg. faDirectory+faArchive+faHidden

Each parameter is a bit-flag, and faAnyFile is simply the combination of all these flags added together.
 
But I think in theory it should work by combining filters in the FindFirst call, eg. faDirectory+faArchive+faHidden

You would think so, logically. Like with the example call, I would expect it to return everything with a Directory attribute, but it failed to return 2 directories and returned 4 files.

For the project I'm working on, I've been looking into findfirst/findnext, and it doesn't look too hard to do for the Windows API calls involved, so I might write a more strict filtering version...
 
so I might write a more strict filtering version...

I'm sharing this code. It seems to work well for me in the testing I've done on it. Hope it helps someone. By all means let me know if you try it and run into problems.

Code:
unit sfffn;
  { unit to encapsulate new FindFirst/FindNext/FindClose.  Certain
    optimizations were made compared to the sysutil versions - most
    important being the fact that the functions in this unit will
    *STRICTLY* filter attributes presented to it.  So don't expect this
    version to act like the Sysutils version.

    Example: faDirectory in Attr only returns directories.
             faHidden+faSysFile only returns files /directories
             with BOTH attributes

    This means there shouldn't be a need for post-filtering unless you
    want to "OR" the attributes - like if you want to return either
    faHidden or faSysFile.  To that end, this should present an
    improvement in that realm.

    The relevant definitions from Sysutils were copied into this source file
    so the added weight of Sysutils is not necessary if all you do is
    search files.  Certain helps were also gotten from the Sysutils source.

    Functions in this unit:
    SFindFirst; Like the FindFirst
    SFindNext; Like the FindNext
    SFindClose; Like the FindClose }

  interface
    uses windows;
    const
      faReadOnly  = $00000001;
      faHidden    = $00000002;
      faSysFile   = $00000004;
    { faVolumeID  = $00000008; faVolumeID seems not used in Windows anyway }
      faDirectory = $00000010;
      faArchive   = $00000020;
      faAnyFile   = $0000003F;
    type
      TFileName = string;
      LongRec = packed record
         Lo, Hi: Word;
      end;
      TSearchRec = record
        Time: Integer;
        Size: Integer;
        Attr: Integer;
        Name: TFileName;
        ExcludeAttr: Integer;
        FindHandle: THandle;
        FindData: TWin32FindData;
      end;

  function SFindFirst(const Path: string; Attr: Integer;
                      var F: TSearchRec): Integer;
  function SFindNext(var F: TSearchRec): Integer;
  procedure SFindClose(F: TSearchRec);

  implementation

procedure move_rec(var F: TSearchRec);
  var
    LocalFileTime: TFileTime;
  begin
    with F do
      begin
        Size := FindData.nFileSizeLow;
        Attr := FindData.dwFileAttributes;
        Name := FindData.cFileName;
        FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
        FileTimeToDosDateTime(LocalFileTime, LongRec(F.Time).Hi,
            LongRec(F.Time).Lo);
      end;
  end;

function SFindFirst(const Path: string; Attr: Integer;
                      var F: TSearchRec): Integer;
  const
    faSpecial = faHidden or faSysFile or faDirectory;
  var
    ret: boolean;
  begin
    if Attr = faAnyFile then
      F.ExcludeAttr := not Attr and faSpecial
    else
      F.ExcludeAttr := Attr;
    F.FindHandle := FindFirstFile(PChar(Path), F.FindData);
    if F.FindHandle <> INVALID_HANDLE_VALUE then
      begin
        while (F.FindData.dwFileAttributes and F.ExcludeAttr) <> F.ExcludeAttr do
          begin
            ret := FindNextFile(F.FindHandle, F.FindData);
            if (not ret) then exit;
          end;
        move_rec(F);
        Result := 0;
      end
    else
      Result := GetLastError;
  end;

  function SFindNext(var F: TSearchRec): Integer;
    var
      ret: boolean;
    begin
      repeat
        ret := FindNextFile(F.FindHandle, F.FindData);
        if (not ret) then break;
      until (F.FindData.dwFileAttributes and F.ExcludeAttr) = F.ExcludeAttr;
      move_rec(F);
      if ret then
        Result := 0
      else
        Result := GetLastError;
    end;

  procedure SFindClose(F: TSearchRec);
    { copied from SysUtils source - nothing changes for this }
    begin
      if F.FindHandle <> INVALID_HANDLE_VALUE then
        Windows.FindClose(F.FindHandle);
    end;

end.
 
I thought of a test case that the above code didn't work on. Hopefully this is better.

Code:
function SFindFirst(const Path: string; Attr: Integer;
                      var F: TSearchRec): Integer;
  const
    faSpecial = faHidden or faSysFile or faDirectory;
  var
    ret: boolean;
  begin
    if Attr = faAnyFile then
      F.ExcludeAttr := not Attr and faSpecial
    else
      F.ExcludeAttr := Attr;
    F.FindHandle := FindFirstFile(PChar(Path), F.FindData);
    if F.FindHandle <> INVALID_HANDLE_VALUE then
      begin
        while (F.FindData.dwFileAttributes and F.ExcludeAttr) <> F.ExcludeAttr do
          begin
            ret := FindNextFile(F.FindHandle, F.FindData);
            if (not ret) then
              begin
                Result := GetLastError;
                exit;
              end;
          end;
        move_rec(F);
        Result := 0;
      end
    else
      Result := GetLastError;
  end;
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top