Home > Software engineering >  What's the fastest way to list all the exe files in a huge directory in Delphi?
What's the fastest way to list all the exe files in a huge directory in Delphi?

Time:02-06

At the moment I'm doing something like this:

var
    Files: TArray<String>;

if IncludeSubDirs then
    Files := TDirectory.GetFiles(Path, '*.exe', TSearchOption.soAllDirectories)
else
    Files := TDirectory.GetFiles(Path, '*.exe', TSearchOption.soTopDirectoryOnly);

Path is a user defined String that can point to any existing directory. For "big" directories with a ton of files and with IncludeSubDirs = True (C:\Windows\ for example) GetFiles takes a very long time (like 30 secs).

What would be the fastest way to list all the exe files in a "big" directory under Windows with Delphi (if any)?

CodePudding user response:

I did some benchmarking and for a huge directory FindFirst / FindNext is about 1.5 to 3% faster than using TDirectory. I would say than both are equivalent in speed (for my use case I saved about 1 sec per minute). I ended up using FindFirst / FindNext since you get results progressively and not all at once, memory management seemed better and it's easier to cancel midway. I also used a TThread to avoid blocking my UI.

This is what I ended up with:

procedure TDirectoryWorkerThread.AddToTarget(const Item: String);
begin
    if (not Self.Parameters.DistinctResults) or (Self.Target.IndexOf(Item) = -1) then
        Self.Target.Add(Item);
end;

procedure TDirectoryWorkerThread.ListFilesDir(Directory: String);

var
    SearchResult: TSearchRec;

begin
    Directory := IncludeTrailingPathDelimiter(Directory);

    if FindFirst(Directory   '*', faAnyFile, SearchResult) = 0 then
    begin
        try
            repeat
                if (SearchResult.Attr and faDirectory) = 0 then
                begin
                    if (Self.Parameters.AllowedExtensions = nil) or (Self.Parameters.AllowedExtensions.IndexOf(ExtractFileExt(SearchResult.Name)) <> -1) then
                        AddToTarget(Directory   SearchResult.Name);
                end
                else if Self.Parameters.IncludeSubDirs and (SearchResult.Name <> '.') and (SearchResult.Name <> '..') then
                    ListFilesDir(Directory   SearchResult.Name);
            until Self.Terminated or (FindNext(SearchResult) <> 0);
        finally
            FindClose(SearchResult);
        end;
    end;
end;

CodePudding user response:

The fastest way is to virtualize the list and use the Windows API. This excerpt of code only shows the file name. Tested with D11. "ListView" must have the property "OwnerData = True", use the metod "onData" and "ViewStyle = vsReport". In "Edit1.Text" there is the folder to show.

USES ShlObj, ComObj, ActiveX

TYPE PShellItem = ^TShellItem;
     TShellItem = Record
                  FullID      : PItemIDList;
                  ID          : PItemIDList;
                  Empty       : Boolean;
                  DisplayName : String;
                  TypeName    : String;
                  ImageIndex  : Integer;
                  Size        : Integer;
                  Attributes  : Integer;
                  ModDate     : String;
                  End;

  TForm1 = class(TForm)
    Button1: TButton;
    ListView: TListView;
    Edit1: TEdit;
    procedure Button1Click(Sender: TObject);
    procedure ListViewData(Sender: TObject; Item: TListItem);
  private
    
    FIDesktopFolder : IShellFolder;
    FIShellFolder   : IShellFolder;
    FIDList         : TList;
    PROCEDURE SetPath(ID : PItemIDList);
    PROCEDURE PopulateIDList(ShellFolder : IShellFolder);
     FUNCTION ShellItem(Index : Integer) : PShellItem;
  public
   
  end;

[...]


procedure TForm1.Button1Click(Sender: TObject);
      VAR P        : PWideChar;
          NewPIDL  : PItemIDList;
          Flags    : LongWord;
          NumChars : LongWord;
    begin
          OLECheck(SHGetDesktopFolder(FIDesktopFolder));
          FIDList := TList.Create;

          NumChars := Length(Edit1.Text);
          Flags := 0;
          P := StringToOleStr(Edit1.Text);
          OLECheck(FIDesktopFolder.ParseDisplayName(Application.Handle,nil,P,NumChars,NewPIDL,Flags));
          SetPath(NewPIDL);
    end;

PROCEDURE TForm1.SetPath(ID: PItemIDList);
      VAR NewShellFolder : IShellFolder;
    BEGIN
          OLECheck(FIDesktopFolder.BindToObject(ID,nil,IID_IShellFolder,Pointer(NewShellFolder)));
          ListView.Items.BeginUpdate;

          Try PopulateIDList(NewShellFolder);

              If ListView.Items.Count>0 Then
                 Begin
                 ListView.Selected := ListView.Items[0];
                 ListView.Selected.Focused := True;
                 ListView.Selected.MakeVisible(False);
                 End;
              Finally
              ListView.Items.EndUpdate;
          End;
    END;

PROCEDURE TForm1.PopulateIDList(ShellFolder: IShellFolder);
    CONST Flags      = SHCONTF_FOLDERS or SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN; //cartelle
          Flags1     = SHCONTF_NONFOLDERS or SHCONTF_INCLUDEHIDDEN; //senza cartelle
      VAR ID         : PItemIDList;
          EnumList   : IEnumIDList;
          NumIDs     : LongWord;
          SaveCursor : TCursor;
          ShellItem  : PShellItem;

 function GetDisplayName(ShellFolder : IShellFolder;
                         PIDL        : PItemIDList): string;
      var StrRet : TStrRet;
          P      : PChar;
          Flags  : Integer;
    begin
          Result := '';
          Flags := SHGDN_FORPARSING; //per vedere le estensioni anche quando sono nascoste

          ShellFolder.GetDisplayNameOf(PIDL, Flags, StrRet);
          case StrRet.uType Of
              STRRET_CSTR : SetString(Result, StrRet.cStr, Length(StrRet.cStr));
            STRRET_OFFSET : Begin
                            P:[email protected][StrRet.uOffset-SizeOf(PIDL.mkid.cb)];
                            SetString(Result,P,PIDL.mkid.cb-StrRet.uOffset);
                            End;
              STRRET_WSTR : Result:=StrRet.pOleStr;
          end;

          Result := ExtractFileName(Result); //solo il nome
    end;

    BEGIN
          SaveCursor := Screen.Cursor;
          Try Screen.Cursor := crHourglass;
              OleCheck(ShellFolder.EnumObjects(Application.Handle,Flags1,EnumList));
              FIShellFolder := ShellFolder;

              While EnumList.Next(1,ID,NumIDs)=S_OK do
                 Begin
                 ShellItem := New(PShellItem);
                 ShellItem.ID := ID;
                 ShellItem.DisplayName := GetDisplayName(ShellFolder,ID);
                 ShellItem.Empty := True;
                 FIDList.Add(ShellItem);
                 End;

              ListView.Items.Count := FIDList.Count;
              ListView.Repaint;
              Finally
              Screen.Cursor:=SaveCursor;
          End;
    END;

 FUNCTION TForm1.ShellItem(Index : Integer) : PShellItem;
    BEGIN
          Result := PShellItem(FIDList[Index]);
    END;

 function IsFolder(ShellFolder: IShellFolder; ID: PItemIDList): Boolean;
      var Flags : UINT;
    begin
          Flags := SFGAO_FOLDER;
          ShellFolder.GetAttributesOf(1, ID, Flags);
          Result := SFGAO_FOLDER and Flags <> 0;
    end;

procedure TForm1.ListViewData(Sender: TObject; Item: TListItem);
      var Attrs : string;
    begin
          if (Item.Index>FIDList.Count) then Exit;

          with ShellItem(Item.Index)^ do
             begin
             Item.Caption := DisplayName;
             Item.ImageIndex := ImageIndex;

             if ListView.ViewStyle<>vsReport then Exit;

             if not IsFolder(FIShellFolder, ID)
                then Item.SubItems.Add(IntToStr(Size) ' Kb')
                else Item.SubItems.Add('');

             Item.SubItems.Add(TypeName);

             try Item.SubItems.Add(ModDate);
                 except
             end;
             if Bool(Attributes and FILE_ATTRIBUTE_READONLY) then Attrs:=Attrs 'R';
             if Bool(Attributes and FILE_ATTRIBUTE_HIDDEN) then Attrs:=Attrs 'H';
             if Bool(Attributes and FILE_ATTRIBUTE_SYSTEM) then Attrs:=Attrs 'S';
             if Bool(Attributes and FILE_ATTRIBUTE_ARCHIVE) then Attrs:=Attrs 'A';
             end;
          Item.SubItems.Add(Attrs);
    end;
  •  Tags:  
  • Related