Autor Beitrag
Popov
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Do 05.02.04 19:48 
Mit dieser Funktion kann man die Größe einer Datei ermitteln:

Erwartet wird ein Parameter:
  • Dateiname mit Pfad

Wird keine Datei gefunden, dann gibt es einen negativen Wert als Rückgabewert (kleiner Null). Ist die Funktion erfolgreich, dann ist das Ergebnis die Größe der Datei in Bytes:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function GetFileSizeA(const FileName: String): Integer; 
var SR: TSearchRec; 
begin 
  Result := -1
   
  if FindFirst(FileName, faAnyFile and not faDirectory, SR) = 0 then 
  try  
    Result := SR.Size; 
  finally SysUtils.FindClose(SR) end
end;


Beispiel:

Hier wird die Größe von AutoExec.bat ermittelt:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(IntToStr( GetFileSizeA('c:\autoexec.bat') ));
end;

_________________
Popov


Zuletzt bearbeitet von Popov am Mo 09.02.04 22:58, insgesamt 2-mal bearbeitet

Für diesen Beitrag haben gedankt: Jakane
MSCH
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1448
Erhaltene Danke: 3

W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
BeitragVerfasst: Do 05.02.04 21:47 
gehe ich richtig in der Annahme, dass wenn die datei nicht gefunden wird -weil nicht vorhanden - findclose() nicht aufgerufen wird? Das ist aber nicht schick, weil, dann gibts Speicherlecks.
grez
msch

_________________
ist das politisch, wenn ich linksdrehenden Joghurt haben möchte?
Popov Threadstarter
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Do 05.02.04 23:18 
Du hast Recht - was soll ich sagen - du hast Recht. Noch 'ne Steigerung: im Programm, für das ich die Funktion geschreiben habe, ist es richtig. Das Try-Finally ist dafür gedacht es zu schützen. Allerdings hab ich den Code für den FAQ-Beitrag ein wenig umformatier und dabei den Fehler gemacht. Ist aber jetzt beseitigt.

_________________
Popov
AndyB
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1173
Erhaltene Danke: 14


RAD Studio XE2
BeitragVerfasst: Do 05.02.04 23:58 
Und
MSCH hat folgendes geschrieben:
gehe ich richtig in der Annahme, dass

ihr weder den RTL QuellCode in SysUtils.pas noch die Win32SDK Hilfe bzw. das PSDK konsultiert habt?
Wenn FindFirst (bzw. Windows.FindFirstFile) einen Wert <>0 liefert, dann muss FindClose nicht unbedingt aufgerufen werden.

Bei dem jetzigen Code (try if FindFirst ... finally FindClose) kann es passieren, dass FindClose mit einem nicht initialisierten TSearchRec aufgerufen wird und somit vielleicht ein anderes "File Search Handle" freigegeben wird, dessen Wert zufällig auf dem Stack war.

_________________
Ist Zeit wirklich Geld?
Popov Threadstarter
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Fr 06.02.04 04:00 
AndyB hat folgendes geschrieben:
Bei dem jetzigen Code (try if FindFirst ... finally FindClose) kann es passieren, dass FindClose mit einem nicht initialisierten TSearchRec aufgerufen wird und somit vielleicht ein anderes "File Search Handle" freigegeben wird, dessen Wert zufällig auf dem Stack war.


Klingt logisch, allerding ...

Ich hab nicht in der SysUtils.pas nachgeguckt, da ich keine Version habe, die eine SysUtils.pas anbietet, sondern nur eine SysUtils.dcu.

Ich nicht die Win32SDK Hilfe bzw. das PSDK konsultiere, da ich mit Delphi arbeite um mich nicht mit der API rumschlagen zu müssen. Was wäre der Sinn eines so tollen Werkzeugs, wenn ich mich bei jeder Funktion zuerst in der API oder einer Unit vergewissern müßte, was für kritische Auswirkungen der Einsatz der Funktion hat.

Ich der Meinung bin, daß in einer Hilfe solche Hinweis stehen müßten, wenn es so wäre.

Ich es getestet habe und nichts passiert ist. Ich habe zwei mal die Funktion aufgerufen, wobei die Zweite keine Datei gefunden hat. Es wurde allerdings kein Fehler ausgegeben. Dabei wurde FindClose zumindest ein mal ausgeführt. Hier der Test den ich ausgeführt habe:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
var
  SR1: TSearchRec;
  SR2: TSearchRec;
  Result: Integer;
begin
  try if FindFirst(ParamStr(0), faAnyFile, SR1) = 0 then
    try if FindFirst(ChangeFileExt(ParamStr(0), '.Test'), faAnyFile, SR2) = 0 then
      Result := SR2.Size;
    finally SysUtils.FindClose(SR2) end;

    Result := SR1.Size;
  finally SysUtils.FindClose(SR1) end;

  ShowMessage(IntToStr( Result ));
end;


Hier ein weiterer Test; noch brutaler:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
var
  SR1: TSearchRec;
  SR2: TSearchRec;
  Result: Integer;
begin
  try if FindFirst(ParamStr(0), faAnyFile, SR1) = 0 then
    SysUtils.FindClose(SR2);
    Result := SR1.Size;
  finally SysUtils.FindClose(SR1) end;

  ShowMessage(IntToStr( Result ));
end;


Natürlich kann es ein Zufall sein. Aber ich kann es nicht sagen.

Andereseit wird in der Hilfe empfohlen jeden aufruf von FindFirst oder FindNext mit FindClose zu beenden, also unabhängig davon ob FindFirst was gefunden hat. Deshalb tendiere ich dazu den FindFirst im Try-Finally Block einzuschließen, denn so wird FindClose auf jeden Fall ausgeführt.

Allerdings kenne ich dich nicht als eine Person, die Unsinn von sich gibt. Wenn du also mehr weißt oder sicher bist, dann sag was deiner Meinung (bzw. dessen was du rausgefunden hast) richtig ist. Ich hab keine Lußt mir die ganze SysUtils & Api reinzuziehen um zu erfahren wqs richtig ist.

Es ist doch so, daß FindFirst & Co. keine seltene Funktionen sind. Millonen Menschen arbeiten täglich damit und millionen Dateien werden nicht von FindFirst gefunden. Stell dir vor jetzt tritt das ein was du gesagt hast. Ich schätze, daß es millionen Webseits gebe die davor warnen FindClose einzusetzen.

_________________
Popov
AndyB
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1173
Erhaltene Danke: 14


RAD Studio XE2
BeitragVerfasst: Fr 06.02.04 10:21 
Popov hat folgendes geschrieben:
AndyB hat folgendes geschrieben:
Bei dem jetzigen Code (try if FindFirst ... finally FindClose) kann es passieren, dass FindClose mit einem nicht initialisierten TSearchRec aufgerufen wird und somit vielleicht ein anderes "File Search Handle" freigegeben wird, dessen Wert zufällig auf dem Stack war.


[...]

Stell dir vor jetzt tritt das ein was du gesagt hast. Ich schätze, daß es millionen Webseits gebe die davor warnen FindClose einzusetzen.

Das tritt nur ein, wenn FindFirst eine Exception wirft, da irgendein Eingabeparameter auf ungültigen Speicher zeigt.
Wie z.B. dem hier mutwillig provoziertem Fehler:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
var
  sr: TSearchRect;
  Path: string;
begin
  Pointer(Path) := Pointer($1);
  try
    if FindFirst(Path, faAnyFile, sr) = 0 then
     ...
  finally
    FindClose(sr); { sr.Handle wurde nie ein Wert in FindFirst zugewiesen }
  end;
end;

sr.Handle wurde nie ein Wert in FindFirst zugewiesen, da Windows.FindFirstFile beim Versuch den String zu lesen eine Schutzverletzung produziert und somit zu unserem finally-Block gesprungen wird. In sr.Handle steht nun irgendein Wert, der eben gerade auf dem Stack liegt. Wenn nun eine weitere FindFirst/Next/Close Anweisung (z.B. Rekursives Einlesen der Ordnerstruktur) ein Handle hat, das gerade diesem Wert auf dem Stack entspricht, wird mit FindClose diese Handle "abgeschossen" und das FindNext für das vorherige Handle liefert 0 womit die Schliefe beendet wird und FindClose erneut auf das nicht mehr vorhandene "Search Handle" angewand wird.

Aber Delphi schützt mit seinem String-Konzept uns vor solchen Fehlern recht gut.


Darum würde ich das so schreiben:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
function GetFileSizeA(const FileName: String): Integer;
var 
  SR: TSearchRec;
begin
  Result := -1;
  if FindFirst(FileName, faAnyFile and not faDirectory, SR) = 0 then
  try
    Result := SR.Size;
  finally 
    SysUtils.FindClose(SR)
  end;
end;

_________________
Ist Zeit wirklich Geld?
MSCH
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1448
Erhaltene Danke: 3

W7 64
XE2, SQL, DevExpress, DevArt, Oracle, SQLServer
BeitragVerfasst: Fr 06.02.04 12:58 
warum nicht einfach so?

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
function GetFileSizeA(const FileName: String): Integer; 
var  
  SR: TSearchRec; 
begin 
  Result := -1
  try 
    if FindFirst(FileName, faAnyFile and not faDirectory, SR) = 0 then 
      Result := SR.Size; 
  finally  
    SysUtils.FindClose(SR) 
  end
end;

grez
msch

_________________
ist das politisch, wenn ich linksdrehenden Joghurt haben möchte?
AndyB
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1173
Erhaltene Danke: 14


RAD Studio XE2
BeitragVerfasst: Fr 06.02.04 13:24 
MSCH hat folgendes geschrieben:
warum nicht einfach so?

Weil du meinen Text nicht gelesen hast.

_________________
Ist Zeit wirklich Geld?
Klabautermann
ontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic star
Veteran
Beiträge: 6366
Erhaltene Danke: 60

Windows 7, Ubuntu
Delphi 7 Prof.
BeitragVerfasst: Fr 06.02.04 13:34 
Hallo,

im zweifelsfalle immer gucken wie es die Borländer machen, in der Delphi-Hilfe findet man zu FindFirst folgendes Beispiel:
Delphi 7 Hilfe hat folgendes geschrieben:
ausblenden volle Höhe Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
procedure TForm1.Button1Click(Sender: TObject);

var
  sr: TSearchRec;
  FileAttrs: Integer;
begin
  StringGrid1.RowCount := 1;
  if CheckBox1.Checked then
    FileAttrs := faReadOnly
  else
    FileAttrs := 0;
  if CheckBox2.Checked then
    FileAttrs := FileAttrs + faHidden;
  if CheckBox3.Checked then
    FileAttrs := FileAttrs + faSysFile;
  if CheckBox4.Checked then
    FileAttrs := FileAttrs + faVolumeID;
  if CheckBox5.Checked then

    FileAttrs := FileAttrs + faDirectory;
  if CheckBox6.Checked then
    FileAttrs := FileAttrs + faArchive;
  if CheckBox7.Checked then

    FileAttrs := FileAttrs + faAnyFile;

  with StringGrid1 do
  begin
    RowCount := 0;

    if FindFirst(Edit1.Text, FileAttrs, sr) = 0 then

    begin
      repeat
        if (sr.Attr and FileAttrs) = sr.Attr then
        begin
        RowCount := RowCount + 1;
        Cells[1,RowCount-1] := sr.Name;
        Cells[2,RowCount-1] := IntToStr(sr.Size);
        end;
      until FindNext(sr) <> 0;
      FindClose(sr);
    end;
  end;
end;


Wie man sieht wird FindClose nur dann aufgerufen wenn FindFirrst erfolgreich war. Warum das so ist hat AndyB ja bereits erklährt. FindFirrst Reserviert kein TSearchRec wenn es ncihts findet.

Gruß
Klabautermann
Popov Threadstarter
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Mo 09.02.04 10:05 
@AndyB

Ich hab deine Hinweise in einem Test überprüft. Ich hab deinen Fehlerquellcode ausgeführt, jedoch mit zwei FindFirst Abfragen, wobei dein Fehlerquellcode innerhalb eines korrekten Quellcode war. Nachdem ich es gestartet habe und ein mal FindClose ausgeführt wurde (der innerhalb deines Beispielcodes), wurde mein Result := SR1.Size ausgeführt. Wie erwartet gab es einen Fehler. Das Problem ist aber, daß es auf jeden Fall einen Fehler gibt; egal wo ich FindFirst bei mir setzte (ob vor Try oder hinter Try). Beide male wird eine Fehlermeldung ausgegeben. Das bedeutet, daß du zwar eine Möglichkeit gezeigt hast wie man eine Fehler bei TSearchRec herstellt, das ändert aber nichts daran, daß man es besser machen kann. Ob vor Try oder hinter Try - das Ergebnis ist immer falsch, bzw. es gibt immer eine Exeption.

Hier mein Beispiel:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
function Beispiel(const FileName: String): Integer;
var
  SR1, sr: TSearchRec;
  Path: string;
begin
  Result := -1;
  Pointer(Path) := Pointer($1);

  if FindFirst(FileName, faAnyFile and not faDirectory, SR1) = 0 then
  try //if FindFirst(FileName, faAnyFile and not faDirectory, SR1) = 0 then

    if FindFirst(Path, faAnyFile, sr) = 0 then
    try // FindFirst(Path, faAnyFile, sr) = 0 then ShowMessage( 'Hallo!' );

      //...
    finally
      FindClose(sr);  { sr.Handle wurde nie ein Wert in FindFirst zugewiesen }
    end;

    Result := SR1.Size;

  finally SysUtils.FindClose(SR1) end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ShowMessage(IntToStr( Beispiel(ParamStr(0) )));
end;


Am Ergebnis kann man also nicht machen. Auch kann man erwarten, daß diese Art von Fehler bei meiner Funktion nicht vorkommen wird. Sollte es unter schlimmsten Umständen dennoch dazu kommen, dann ändert die Reihenfollge auch nichts.

Aber jetzt interresiert es mich doch wie die korrekte Reihenfollge ist (es geht hier nicht mehr um das Beispiel, sondern um FindFirst allgemein). In der Hilfe steht, daß FindFirst Speicher reserviert, der durch FindClose freigegeben werden muß:

Delphihilfe hat folgendes geschrieben:
FindFirst belegt Ressourcen (Speicher), die durch einen Aufruf von FindClose wieder freigegeben werden müssen.


Das heißt für mich, daß FindFirst auf jeden Fall Speicher belegt, egal ob FindFirst erfolgreich war oder nicht. Ich hab keine Quellen wo ich was anderes nachlesen kann. Nun könnte man sagen, daß es dann egal ist wo FindFirst. Das Problem ist, daß wenn FindFirst (nur durch Aufruf) schon Speicher belegt, dann wäre es fahrlässig FindFirst vor dem Try zu setzten. Findet FindFirst keine Datei, dann wird kein FindClose ausgeführt und der Speicher wird nie freigegeben. Belegt FindFirst den Speicher erst wenn er erfolgreich war, dann ist es fast egal wo FindFirst steht. Es ist nicht zu erwarten, daß Delphi da einen Fehler macht und eine falsche Variable freigibt (ausgenohmen natürlich, wenn dein Fehler auftritt).

Vorerst steht es 1:0 für FindFirst innerhalb von Try-Finally, da, für den Fall, daß FindFirst auf jeden Fall Speicher belegt, so immer FindClose ausgeführt wird. Aber ich bin nicht drauf versesen es so zu machen. Dann muß ich aber eine klare Antwort haben. Ich hab die Delphihilfe und den Win32 FindFirst Abschnitt gelesen und es steht nichts zu den Thema. Wenn du also mehr weißt, dann sag es klar. Sag dann aber auch wo man es nachlesen kann. Mir ist es schon klar, daß es bei Objekten so ist:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
  variable := typename.Create;
  try

  finally
    variable.Free;
  end;


und nicht so:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
  try
    variable := typename.Create;

  finally
    variable.Free;
  end;


Die frage ist aber ob man das vergleichen kann. Wie gesagt, ohne eine konkrete Aussage mit Quellangabe werde ich an der oberen Funktion nichts ändern, bzw. an der Reihenfollge nichts ändern. Was ich aber machen werde ist das:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function GetFileSizeA(const FileName: String): Integer;
var SR: TSearchRec;
begin
  if FileExists(FileName) then begin
    try if FindFirst(FileName, faAnyFile, SR) = 0 then
      Result := SR.Size;
    finally SysUtils.FindClose(SR) end;
  end
    else Result := -1;
end;


Ist also doppelte Abfrage.

Ich hab aber keine Lust es jedes mal so zu machen und ich hab vor noch einige Tipps zu schreiben. Ich hab aber keine Lust wieder mit der Frage "Ist das wirklich richtig? Hast du dir mal die Win32 durchgelesen?" konfrontiert zu werden. Keine Fragen in der Zukunft. Entweder eine konkrete Ausage mit Quellangabe oder garnichts.


ALTERNATIV könnte man es testen. Ich kann es nicht machen, da mein System mommentan spinnt. Einfach FindFirst 1000000 mal ausführen und gucken ob Speicher belegt wird oder nicht:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
var
  k: Integer;
  SR: TSearchRec;
begin
  for k := 1 to 1000000 do
    if FindFirst('KeinName', faAnyFile, SR) = 0 then ShowMessage( 'Hallo!' );
    //if not (FindFirst(ParamStr(0), faAnyFile, SR) = 0) then ShowMessage( 'Hallo!' );
end;


Unter XP kann man sehr schön sehen ob der Speicherverbrauch in die Höhe geht.

_________________
Popov
Popov Threadstarter
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Mo 09.02.04 10:33 
Also gut, ich hab beide Varianten ausprobiert. Weder an der Windows-Ressorcenanzeige, noch am Systemmonitor ( beide gehören zu Windows 98 ), hat sich je was getann. Kein zusätzlicher Speicherverbrauch bei eine million Aufruffen. Andererseits ist bei der zweiten Variante mein Rechner abgeschmiert. Allerdnings weiß ich nicht ob das ein Beweis ist, daß nur bei erfollgreichen FindFirst Speicher verbraucht wird.

_________________
Popov
MathiasSimmack
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Mo 09.02.04 11:44 
Titel: SysUtils vs. API?
Das D7-Beispiel, das Klabautermann zitiert hat, findet man ja auch in der Hilfe von Delphi 5. Und ich weiß auch, dass ich den Code von "FindClose" schon mal gepostet habe, weil auch jemand sagte, ich müsse vorher prüfen ob der Wert (= das Handle) überhaupt benutzt wird. Aber das gilt IMHO nur für die API-Funktion, weil Borland in der SysUtils-Unit selbst die Prüfung übernimmt. Hier mal der Auszug:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
function FindFirst(const Path: string; Attr: Integer;
  var F: TSearchRec): Integer;
const
  faSpecial = faHidden or faSysFile or faVolumeID or faDirectory;
begin
  F.ExcludeAttr := not Attr and faSpecial;
  F.FindHandle := FindFirstFile(PChar(Path), F.FindData);
  if F.FindHandle <> INVALID_HANDLE_VALUE then
  begin
    Result := FindMatchingFile(F);
    if Result <> 0 then FindClose(F);
  end else
    Result := GetLastError;
end;

function FindNext(var F: TSearchRec): Integer;
begin
  if FindNextFile(F.FindHandle, F.FindData) then
    Result := FindMatchingFile(F) else
    Result := GetLastError;
end;

procedure FindClose(var F: TSearchRec);
begin
  if F.FindHandle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(F.FindHandle);
    F.FindHandle := INVALID_HANDLE_VALUE;
  end;
end;

Aus dem Grund ist (bzw. war) es für mich (bzw. in meinen Programmen) bisher kein Problem, einfach nur folgenden Code zu benutzen; gänzlich ohne try/finally/end:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
res := FindFirst('*.*',faAnyFile,ds);
while(res = 0do
begin
  // mach was mit dem Record
  { ... }

  res := FindNext(ds);
end;
FindClose(ds);

Für das API gilt das allerdings nicht, hier muss man die Prüfung selbst vornehmen. Will sagen: hier darf man "FindClose" nur aufrufen, wenn das Ergebnis von "FindFirstFile" nicht INVALID_HANDLE_VALUE ist. Darum sieht die Anweisung in meinen nonVCL-Programmen meist so aus:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
res := FindFirstFile('*.*',ds);
if(res <> INVALID_HANDLE_VALUE) then
try
  while(res <> ERROR_NO_MORE_FILES) do begin
    // mach irgendwas
    { ... }

    if(not FindNextFile(res,ds)) then break;
  end;
finally
  FindClose(res);
end;

Das kann man auch im PSDK nachlesen:
PSDK (FindFirstFile) hat folgendes geschrieben:
If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose.

If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError.


Müsste ich also Popovs Funktion "GetFileSizeA" mit den Mitteln von Delphi schreiben, würde ich das (unter Berücksichtigung des Beispiels aus der Hilfe) wahrscheinlich so tun:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
function GetFileSizeA(const FileName: String): Integer;
var
  SR : TSearchRec;
begin
  Result := -1;

  if FindFirst(FileName,faAnyFile and not faDirectory,SR) = 0 then
  begin
    Result := SR.Size;
    {SysUtils.}FindClose(SR);
  end;
end;

Mit dem API sähe es bei mir so aus:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
function GetFileSizeAPI(const szFilename: string): integer;
var
  Handle   : THandle;
  FindData : TWin32FindData;
begin
  Result   := -1;
  Handle   := FindFirstFile(pchar(szFilename),FindData);

  if(Handle <> INVALID_HANDLE_VALUE) then
  begin
    Result := FindData.nFileSizeLow;
    Windows.FindClose(Handle);
  end;
end;
AndyB
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1173
Erhaltene Danke: 14


RAD Studio XE2
BeitragVerfasst: Mo 09.02.04 12:08 
Ich bin zwar der Meinung, dass ich das bereits in einem anderen Wortlaut oben geschrieben habe, will aber jetzt nicht meinen ganzen Text durchlesen.

Zuallererst einmal, was FindFirst und FindClose so macht;
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
function FindFirst(const Path: string; Attr: Integer;
  var  F: TSearchRec): Integer;
const
  faSpecial = faHidden or faSysFile or faVolumeID or faDirectory;
begin
  F.ExcludeAttr := not Attr and faSpecial;
  F.FindHandle := FindFirstFile(PChar(Path), F.FindData);
  if F.FindHandle <> INVALID_HANDLE_VALUE then
  begin
    Result := FindMatchingFile(F); // Löst keine Exception aus
    if Result <> 0 then FindClose(F);  // <--
  end else
    Result := GetLastError;
end;

procedure FindClose(var F: TSearchRec);
begin
  if F.FindHandle <> INVALID_HANDLE_VALUE then
  begin
    Windows.FindClose(F.FindHandle);
    F.FindHandle := INVALID_HANDLE_VALUE;
  end;
end;


Mögliche Exception stellen:
  • F könnte "nil" sein ( FindFirst(x, y, MyRec^); mit MyRec: PSearchRec ) somit kann bei F.ExecuteAttr und F.FindData eine AV ausgelöst werden. Es wurde aber noch kein Speicher reserviert. Da F.FindHandle zwichen F.ExecuteAttr und F.FindData liegt, ist dies durch die beiden "Tests" abgesichert.
  • Pointer(Path) könnte nil sein (mein Beispiel), dann löst Windows.FindFirstFile eine AV aus. Dies passiert aber noch vor dem reservieren von Speicher (="FindHandle") (durch "Debug & Conquer")
  • FindMatchingFile() löst keine Exception aus, da dort nur WinAPI Funktionen aufgerufen werden und alle möglichen AVs bereits durch FindFirstFile und "F." abgedeckt sind.


Für Windows.FindFirstFile steht nun dies im PSDK:
Zitat:
If the function succeeds, the return value is a search handle used in a subsequent call to FindNextFile or FindClose.

If the function fails, the return value is INVALID_HANDLE_VALUE. To get extended error information, call GetLastError.

Daraus kann man folgern, dass ein Windows.FindClose(INVALID_HANDLE_VALUE) nicht viel macht. Wenn also Windows.FindFirstFile keine Datei findet (fails), dann steht in F.FindHandle INVALID_HANDLE_VALUE und die FindFirst wird mit GetLastError als Result verlassen. Ein folgender Aufruf von FindClose() ist nicht notwendig.
Somit gelangt man schon mal zu folgendem Code:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
if FindFirst(x, y, sr) = 0 then
begin
  // ...
  FindClose(sr);
end;

Wenn aber Windows.FindFirstFile ein gültiges FindHandle zurückliefert, dann wird FindMatchingFile aufgerufen, dass einem FindNext entspricht. Liefert dies einen Wert <> 0 (der wirkliche Wert ist GetLastError), so wurde keine weitere Datei gefunden (Windows.FindNextFile lieferte False), also wird das FindHandle von FindFirst freigegeben durch einen Aufruf von FindClose, womit kein weiterer FindClose-Aufruf nötog ist, aber nicht zu einem Fehler führt, da FindClose das Feld F.FindHandle auf INVALID_HANLDE_VALUE setzt, was zu einem Windows.FindClose(INVALID_HANDLE_VALUE) führen würde, wenn FindClose das nicht abprüfen würde.

Wenn man dieses Wissen nun anwendet, kann man feststellen, dass wenn in "sr: TSearchRec" keine Zuweisung an "sr.FindHandle := INVALID_HANDLE_VALUE" stattgefunden hat, steht in sr.FindHandle irgend ein Wert, der unglücklicher weise ein FindHandle für eine andere Suchaktion (z.B. rekursiver Aufruf) sein kann. Wenn man nun FindClose in jedem Fall (egal ob FindFirst = 0 oder <> 0 liefert) aufruft, kann es passieren das eine andere Suchaktion abgebrochen wird, weil ihr FindHandle von FindClose an Windows.FindClose übergeben wurde.

Und nun gelangt man zu folgendem Code:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
if FindFirst(x, y, sr) = 0 then
begin
  try
    // ...
  finally
    FindClose(sr);
  end;
end;



Um es noch einmal gänzlich zu verdeutlichen warum nicht "try FindFirst finally FindClose"
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
var
  sr: TSearchRec; // Speicher auf dem Stack, in dem irgendwas steht
  s: string;
begin
  Pointer(s) := Pointer($1);
  try
    if FindFirst(s, faAnyFile, sr) = 0 then // löst AV aus, bevor sr.FindHandle einen echten Wert bekommt
    begin
      // ..
    end;
  finally
    FindClose(sr); { In sr.FindHandle steht irgend ein Wert, der gerade an dieser Stelle auf dem Stack lag. Hierdurch wird also ein falsches FindHandle freigegeben, vorausgesetzt es war im gesamten Prozess gültig war. Und diese Ungewissheit kann man vor allem bei Rekursion und Erweiterbarkeit nicht hinnehmen. }
  end;
end;


Wenn man Beispiel nun doch benutzen will, muss man vor dem Eintitt in den try-finally Block sr.FindHandle auf INVALID_HANDLE_VALUE setzen.
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
var
  sr: TSearchRec;
begin
  sr.FindHandle := INVALID_HANDLE_VALUE;
  try
    if FindFirst(Path, faAnyFile and not faDirectoy, sr) = 0 then
    begin
      // ..
    end;
  finally
    FindClose(sr);
  end;
end;


Um zu deiner Analogie zu kommen:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
// mein Beispiel:
var
  sr: TSearchRec;
begin
  if FindFirst(x, y, sr) = 0 then                  Reference := TObject.Create;
  try                                              try
      ;
  finally                                          finally
    FindClose(sr);                                   Reference.Free;
  end;                                             end;
end;



// dein Beispiel:
var
  sr: TSearchRec;
begin
  sr.FindHandle := INVALID_HANDLE_VALUE;           Reference := nil;
  try                                              try
    if FindFirst(x, y, sr) = 0 then                  Reference := TObject.Create;
      ;
  finally                                          finally
    FindClose(sr);                                   Reference.Free;
  end;                                             end;
end;

_________________
Ist Zeit wirklich Geld?
Popov Threadstarter
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Mo 09.02.04 13:17 
Also zuerst mal: Herr Müller-Lüdenscheid, die Ente bleibt drin!

Nicht so:

ausblenden Delphi-Quelltext
1:
{SysUtils.}FindClose(SR);					


sondern so:

ausblenden Delphi-Quelltext
1:
SysUtils.FindClose(SR);					


Die Unit Windows hat auch ein FindClose und es hat mich mal Zeit und Nerven gekostet bis ich das rausgefunden habe. Am Code war nichts falsch, aber der Compiler wollte nicht. Irgendwie ist mal bei mir die SysUtils vor Windows gekommen. Seit dem gehört die SysUtils vor FindFirst.

Zum Thema: das ganze hätten wir auch früher haben können, hätte man mir den Abschnitt da schon gezeigt. Hat man aber nicht. Vielleicht sollte ich mir mal ne alte D5E kaufen (kosten aber auch noch viel Geld).

So wie ich das jetzt aus der SysUtils auslese ist es gehopst wie gesprungen wie es angeordnet wird. Ich kenn zwar die Funktion FindMatchingFile nicht, aber der Rest sagt mir, daß das ganze relatv Narensicher programmiert wurde. Ist das Ergebnis der FindFirstFile ein Error, dann wird ein ErrorCode von Delphi FindFirst zurückgegeben, jedoch kein Exception ausgelöst. Wird kein Error ausgegeben, dann kann es immer noch sein, daß FindClose ausgeführt wird, da wahrscheinlich die Attribute nicht stimmen. Ergebnis von FindFirst kann also sein: ein ErrorCode wenn keine Datei gefunden wurde oder ein ErrorCode mit FindClose wenn keine Attribute übereistimmt haben.

Im Delphi FindClose kann auch nichts passieren, da vorher abgefragt wird ob das Handle <> INVALID_HANDLE_VALUE ist. Ist es, dann wird nichts gemacht. Ist es nicht, dann wird das Handle geschlossen und auf INVALID_HANDLE_VALUE gestellt. Ein Aufrufen von Delphi FindClose kann also nie verkehrt sein. Im Grunde genommen kann ich es 1000 mal aufrufen. Mehr Speicher wird dadurch nicht frei.

Es ist also gehopst wie gesprungen ob FindFirst vor oder hinter Try steht. Ich muß mir das mit FindFirstFile in der Win32.hlp zwar noch mal genauer angucken, aber ich gehe davon aus, daß nichts passiert wenn keine Datei gefunden wird. In diesem Fall würde in der FindFirst Funktion sonnst etwas mehr passieren als nur den ErrorCode übergeben.

FindFirst muß somit nicht geschützt werden. Alles andere aber schon (@Mathias). Die einzige Funktion die nicht geschützt ist, daß ist die FindNext. Die Funktion wird also wieder so aussehen:

ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
function GetFileSizeA(const FileName: String): Integer;
var SR: TSearchRec;
begin
  Result := -1;
  
  if FindFirst(FileName, faAnyFile and not faDirectory, SR) = 0 then
  try 
    Result := SR.Size;
  finally SysUtils.FindClose(SR) end;
end;


Das and not faDirectory klaue ich mir mal einfach.

Damit ist die Diskusion beendet. Schluß, basta, finito 8) .

//EDIT

Ich hab den Text so nach und nach geschrieben, so das ich nicht gesehen habe, daß AndyB auch schon was geschrieben hat. Deshalb bezieht sich mein Text nur auf das Posting von Mathias.

_________________
Popov
Popov Threadstarter
ontopic starontopic starontopic starontopic starhalf ontopic starofftopic starofftopic starofftopic star
Beiträge: 1655
Erhaltene Danke: 13

WinXP Prof.
Bei Kleinigkeiten D3Pro, bei größeren Sachen D6Pro oder D7
BeitragVerfasst: Mo 09.02.04 13:43 
@AndyB

1. Zu denem besonderen Fehler. Wenn der vorkommt, dann nutzt deine Variante auch nichts. Da wird auf jeden Fall eine Exception ausgelöst. Und wenn da einer mit einigen Try-Except's die abfängt, dann merkt man auch nichts und bekommt falsche Werte.

2. Man kann die Pferde auch richtig scheu machen, bzw. übertreiben, bzw. mit Kanonen auf Spatzen geschossen. Es gibt irgend eine Menge an Verantwortungsbewußtsein das man aufbringen sollte. Bei benutzen von FindFirst gleich daran zu denken, daß irgendein Speicherfehler vielleicht vorkommen könnte um deshalb besondere Abfagen einzubauen ist Blödsinn. Noch mal: Blödsinn. Und jetzt will ich nichts hören von weden: dann bist du ein schlampiger Programmierer. Ich muß nicht davon ausgehen, daß wenn ich FindFirst nutze ein Speicherfehler auf mich warten könnte. Ich gehe davon aus, daß da keiner da ist. Und wenn einer da ist, dann Pech gehabet. Dann hab ich aber nicht an dieser Stelle versagt, sondern schon vorher, da ich einen Spaicherfehler zugelassen habe. Aber nicht bei FindFirst.

Aber dennoch hab ich der Richtighalber es so gemacht wie es nicht falsch ist. Wobei innerhalb von Try wäre es vielleicht unnötig, aber nicht falsch. Auch auf die Gefahr hin, daß es einen Speicherfehler geben könnte.

_________________
Popov
MathiasSimmack
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Mo 09.02.04 14:00 
Popov hat folgendes geschrieben:
Nicht so:

ausblenden Delphi-Quelltext
1:
{SysUtils.}FindClose(SR);					

Ich nehme an, das bezog sich auf mich? Dazu muss ich vielleicht erwähnen, dass es bei mir zu keinem Fehler kommt, da sich in der Unitdeklaration die SysUtils-Unit hinter der Windows-Unit befindet. Wenn ich also mit der VCL arbeitet (wobei die SysUtils ja nun nicht wirklich was damit zu tun hat), dann reicht ein einfaches
ausblenden Delphi-Quelltext
1:
FindClose(SR)					

Ich wollte es wie oben gezeigt nur kennzeichnen, damit jeder weiß worauf ich mich beziehe. Auf die "FindClose"-Funktion von Borland nämlich!

@AndyB: Die Zugriffsverletzung in deinem Beispiel kommt nicht durch "FindFirst" oder "FindClose" zustande, sondern sie liegt ganz einfach am:
AndyB hat folgendes geschrieben:
Pointer(s) := Pointer($1);[/delphi]

Würdest du stattdessen nil zuweisen, würdest du auch keine AV sehen, die API-Funktion "FindFirstFile" keine Exception auslöst, sondern einfach INVALID_HANDLE_VALUE als Ergebnis liefert. Und damit verbietet sich der Aufruf von "Windows.FindClose" IMHO von selbst.

Zitat:
Daraus kann man folgern, dass ein Windows.FindClose(INVALID_HANDLE_VALUE) nicht viel macht. Wenn also Windows.FindFirstFile keine Datei findet (fails), dann steht in F.FindHandle INVALID_HANDLE_VALUE und die FindFirst wird mit GetLastError als Result verlassen. Ein folgender Aufruf von FindClose() ist nicht notwendig.

[...]

Wenn aber Windows.FindFirstFile ein gültiges FindHandle zurückliefert, dann wird FindMatchingFile aufgerufen, dass einem FindNext entspricht.

Nur bedingt! Der Unterschied zwischen API und Borland ist doch der, dass die API-Funktion grundsätzlich alles zurückliefert, was dem Suchmuster entspricht. Ob Ordner oder Datei ... Attribute spielen keine Rolle. Borland hingegen erlaubt die Eingrenzung, so dass du bspw. nur Ordner suchen lassen kannst. Solange du keins der speziellen Attribute
SysUtils.pas (FindFirst) hat folgendes geschrieben:
ausblenden Delphi-Quelltext
1:
2:
const
  faSpecial = faHidden or faSysFile or faVolumeID or faDirectory;

verwendest, solange wird die Bedingung in "FindMatchingFile"
SysUtils.pas (FindMatchingFile) hat folgendes geschrieben:
ausblenden Delphi-Quelltext
1:
while FindData.dwFileAttributes and ExcludeAttr <> 0 do					

auch nicht wirksam, und die Funktion übergibt die Daten der gefundenen Datei an das TSearchRec-Record.

Anders sieht es aus, wenn du bspw. nur versteckte Dateien suchen willst. Du übergibst also faHidden (= 2) an die Borland-Funktion, wodurch sich als "F.ExcludeAttr" der Wert 28 ergibt. Diesmal wird die o.g. Bedingung wirksam, und die interne Borland-Funktion "FindMatchingFile" ruft die API-Funktion "FindNextFile" solange auf, bis sie entweder keine Dateien mehr findet, oder bis sie eine hat, die sowohl dem gewünschten Suchmuster entspricht als auch das Hidden-Attribut hat.

Zitat:
Liefert dies einen Wert <> 0 (der wirkliche Wert ist GetLastError), so wurde keine weitere Datei gefunden (Windows.FindNextFile lieferte False), also wird das FindHandle von FindFirst freigegeben durch einen Aufruf von FindClose, womit kein weiterer FindClose-Aufruf nötog ist, [...]

Und damit habe ich, glaube ich, einen Denkfehler von dir gefunden. Du hast evtl. nicht bedacht, dass die API-Funktion (wie schon gesagt!) alles zurückliefert, und das die Eingrenzung auf die Attribute eine Erweiterung von Borland ist.

Wenn die API-Funktion "FindFirstFile" also bspw. eine normale Textdatei zurückliefert, du aber das Hidden-Attribut bei der Suche übergeben hast, dann ist die Suche trotzdem erfolgreich gewesen! Es liegt lediglich an Borlands "FindMatchingFile", dass die normale Textdatei übergangen und solange gesucht wird, bis eine versteckte Textdatei gefunden wird.

Trotzdem ist der Aufruf von "FindClose" notwendig, weil es die API-Funktion "nicht interessiert", welche Dateien mit welchen Attributen dich interessieren.
MathiasSimmack
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Mo 09.02.04 14:39 
Hier muss ich mich korrigieren:
AndyB hat folgendes geschrieben:
[...] also wird das FindHandle von FindFirst freigegeben durch einen Aufruf von FindClose, womit kein weiterer FindClose-Aufruf nötog ist, [...]

Das war mein Denkfehler, denn natürlich wird (wie man ja in Borlands "FindFirst" auch sehen kann) "FindClose" aufgerufen, wenn keine Datei mit dem Suchmuster bzw. den Attributen gefunden werden konnte. Weil "FindMatchingFile" dann ein Ergebnis <> Null (= GetLastError) zurückliefert.

Aber hier bleibe ich dabei:
Zitat:
[...] dann wird FindMatchingFile aufgerufen, dass einem FindNext entspricht.

Das stimmt nicht! "FindMatchingFile" ist kein echtes "FindNext", denn das ist lediglich von den übergebenen Attributen abhängig.

Popov hat folgendes geschrieben:
Ich kenn zwar die Funktion FindMatchingFile nicht, [...]

Kein Problem, dann ist auch alles beieinander. ;)
SysUtils.pas hat folgendes geschrieben:
ausblenden Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
function FindMatchingFile(var F: TSearchRec): Integer;
var
  LocalFileTime: TFileTime;
begin
  with F do
  begin
    while FindData.dwFileAttributes and ExcludeAttr <> 0 do
      if not FindNextFile(FindHandle, FindData) then
      begin
        Result := GetLastError;
        Exit;
      end;
    FileTimeToLocalFileTime(FindData.ftLastWriteTime, LocalFileTime);
    FileTimeToDosDateTime(LocalFileTime, LongRec(Time).Hi,
      LongRec(Time).Lo);
    Size := FindData.nFileSizeLow;
    Attr := FindData.dwFileAttributes;
    Name := FindData.cFileName;
  end;
  Result := 0;
end;
AndyB
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 1173
Erhaltene Danke: 14


RAD Studio XE2
BeitragVerfasst: Mo 09.02.04 23:04 
Zitat:
Vielleicht sollte ich mir mal ne alte D5E kaufen

Wenn du den RTL Code von Delphi 6 haben willst kannst du beim FreeCLX dich durch den CVS Tree angeln. Borland war im Jahr 2001 so nett und hat die VisualCLX samt RTL unter GPL gestellt.

_________________
Ist Zeit wirklich Geld?