AGB  ·  Datenschutz  ·  Impressum  







Anmelden
Nützliche Links
Registrieren
Thema durchsuchen
Ansicht
Themen-Optionen

FindFirstFileNameW aufrufen

Ein Thema von Benmik · begonnen am 11. Jan 2014 · letzter Beitrag vom 13. Jan 2014
Antwort Antwort
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.340 Beiträge
 
Delphi 12 Athens
 
#1

AW: FindFirstFileNameW aufrufen

  Alt 12. Jan 2014, 00:13
Da ich schon mal dabei war, habe ich für CloseFile auch gleich die entsprechende Funktion aus kernel32.dll aufgerufen, da es ja in Delphi für FindFirstFileNameW nichts gibt.
Weil das kein "eigenes" FileClose hat?

Also warum nicht gleich das FileClose aus der Unit Windows nehmen, was Delphi schon bereitstellt?

Eine Sache ist merkwürdig und ärgerlich: Mit Verlassen der Routine '"ZeigeHardlinks" kommt es zu einer Zugriffsverletzung im Modul "oleaut32.dll" (Lesen). Hat da jemand eine Idee zu?
Was hab ich gesagt?
DU mußt den Speicher reservieren, wo der Dateiname reingeschrieben werden soll.

Es steht auch alles ganz genau in der Definition drin.
Zitat:
StringLength [in, out]

The size of the buffer pointed to by the LinkName parameter, in characters. If this call fails and the error returned from the GetLastError function is ERROR_MORE_DATA (234), the value that is returned by this parameter is the size that the buffer pointed to by LinkName must be to contain all the data.
LinkName [in, out]

A pointer to a buffer to store the first link name found for lpFileName.
Du hast weder den "Buffer" reserviert, noch die Größe des Buffers übergeben.

Zitat:
Showmessage(WideCharToString(@PLinkName));
Das ist so richtig?
Ein Zeiger auf den Zeiger ... ich kann mich täuschen, aber ich glaub nicht.

Daß es bei dir "funktionierte, liegt daran, daß du einen Zeiger auf die Variable "@LinkName" und nicht auf den Inhalt "@LinkName[1]" aka "PWideChar(LinkName)" übergeben hast.
In diesen Zeiger passen nur 4 Byte und da braucht man sich nicht wundern, wenn dahinter alles überschrieben wird.

Da war es auch egal, daß du noch keinen Speicher reserviert hattest, da der eh nicht genutzt wurde, aber hier hätte dir die Zugriffsverletzung bei Adresse 0 zu Denken gegeben, wenn du das richtig übergeben hättest.

Delphi-Quellcode:
procedure TForm1.ZeigeHardlinks;
var
  Dateiname : WideString;
  LinkLänge : Cardinal;
  LinkName : WideChar;
  PLinkName : PWideChar;
  Handle : Cardinal;
begin
  Dateiname := 'C:\Temp\Hardlink.JPG';
  SetLength(LinkName, MAX_PATH);
  LinkLänge := Length(LinkName);
  Handle := FindFirstFileNameW(PWideChar(Dateiname), 0, @LinkLänge, PWideChar(LinkName));
  If Handle = INVALID_HANDLE_VALUE // ja, so Einiges kennt Delphi auch schon
    then Showmessage(SysErrorMessage(GetLastError));
  Showmessage(PWideChar(LinkName)); // PChar->String kann Delphi automatisch umwandeln, aber wenn man vorher LinkName auf die "richtige" Länge setzt (bis vor die erste/abschließende #0, dann kann man diese String-Variable auch direkt verwenden)
  LinkLänge := Length(LinkName);
  While FindNextFileNameW(Handle, @LinkLänge, PWideChar(LinkName)) do
  begin
    Showmessage(WideChar(LinkName));
    LinkLänge := Length(LinkName);
  end;
  CloseFile(Handle);
end;


procedure TForm1.ZeigeHardlinks;
var
  Dateiname : WideString;
  LinkLänge : Cardinal;
  PLinkName : PWideChar;
  Handle : Cardinal;
begin
  Dateiname := 'C:\Temp\Hardlink.JPG';
  PLinkName := GetMemory(MAX_PATH * 2 + 2); // +2 für die abschließende #0
  LinkLänge := MAX_PATH;
  Handle := FindFirstFileNameW(PWideChar(Dateiname), 0, @LinkLänge, PLinkName);
  If Handle = INVALID_HANDLE_VALUE
    then Showmessage(SysErrorMessage(GetLastError)); // wieso wird hier eigentlich, auch bei einem Fehler, der nachfolgende Code dennoch ausgeührt?
  Showmessage(PLinkName);
  LinkLänge := MAX_PATH;
  While FindNextFileNameW(Handle, @LinkLänge, PLinkName) do
  begin
    Showmessage(PLinkName);
    LinkLänge := MAX_PATH;
  end;
  FreeMemory(PLinkName);
  CloseFile(Handle);
end;


procedure TForm1.ZeigeHardlinks;
var
  Dateiname : WideString;
  LinkLänge : Cardinal;
  LinkName : array[0..MAX_PATH] of WideChar;
  Handle : Cardinal;
begin
  Dateiname := 'C:\Temp\Hardlink.JPG';
  LinkLänge := MAX_PATH;
  Handle := FindFirstFileNameW(PWideChar(Dateiname), 0, @LinkLänge, @LinkName);
  If Handle = INVALID_HANDLE_VALUE then
    RaiseLastOSError;
  repeat
    ShowMessage(PWideChar(@LinkName));
    LinkLänge := MAX_PATH;
  until not FindNextFileNameW(Handle, @LinkLänge, @LinkName);
  CloseFile(Handle);
end;
Die fehlenden Resourcenschutzblöcke ignorier ich mal.
Und da deine Fehlerbehandlung nicht die Beste ist und der nachfolgende Code sowieso ausgeführt wird, wird das FreeMemory dennoch aufgerufen.
Außer es knallt was ganz schlimmes, wie z.B. bei deinem originalen Code, wo du dir einen netten BufferOverrun gebastelt hattest, aber da ist eh alles zu spät und das kleine Speicherleck stört dann auch nicht mehr.
Ein Therapeut entspricht 1024 Gigapeut.

Geändert von himitsu (12. Jan 2014 um 00:18 Uhr)
  Mit Zitat antworten Zitat
Benmik

Registriert seit: 11. Apr 2009
570 Beiträge
 
Delphi 12 Athens
 
#2

AW: FindFirstFileNameW aufrufen

  Alt 12. Jan 2014, 16:26
Vielen Dank für deine Mühe, besonders dafür, dass du den Code auch noch didaktisch entwickelt hast, und das um 1:18 Uhr nachts. Auch wenn man förmlich die Watschen hört, gefolgt von einem "Sechs! Setzen!". Aber ich weiß genau, dass hier viele mitlesen, die mit denselben Problemen kämpfen und froh sind über jede Aufklärung.
Natürlich war der Code auch nicht sozusagen als fertige Lösung gedacht, er war lediglich als Machbarkeitsstudie intendiert, weil er überhaupt im Prinzip funktionierte.

Ein kleiner Trost ist es, dass der Code vom Meister auch nicht so ohne weiteres läuft.
Bei Handle := FindFirstFileNameW(PWideChar(Dateiname),0,@PDatLänge,@PLinkName); moniert der Compiler, dass die Parameter formal nicht stimmen, genauso wie bei LinkName : array[0..MAX_PATH] of WideChar; .

Daher mein nächster Versuch:
Delphi-Quellcode:
procedure TForm1.ZeigeHardlinks;
var
  Dateiname : WideString;
  PLinkLänge : PDWord;
  PLinkName : PWideChar;
  Handle : Cardinal;
begin
  Dateiname := 'C:\Temp\Hardlink.JPG';
  PLinkName := GetMemory(MAX_PATH * 2 + 2); // +2 für die abschließende #0
  PLinkLänge := GetMemory(SizeOf(PDWord));
  //PLinkLänge^ := MAX_PATH;
  Try
    Handle := FindFirstFileNameW(PWideChar(Dateiname), 0, PLinkLänge, PLinkName);
    If GetLastError = ERROR_MORE_DATA then begin
      Showmessage(SysErrorMessage(GetLastError));
    end else if Handle = INVALID_HANDLE_VALUE then begin
      RaiseLastOSError;
    end else begin
      Repeat
        ShowMessage(PWideChar(@PLinkName));
        //PLinkLänge^ := MAX_PATH;
      Until not FindNextFileNameW(Handle, PLinkLänge, PLinkName);
    end;
  Finally
    FreeMemory(PLinkName);
    FreeMemory(PLinkLänge);
    FileClose(Handle);
  End;
end;
Dieser Code funktioniert in dem Sinn, dass die Namen richtig ausgegeben werden. Bei FreeMemory(PLinkName); knallt es aber. Schleierhaft, wieso.

Die Zuweisung PLinkLänge^ := MAX_PATH; funktioniert, ist aber unnötig, denn - wie du ja auch ausführst - wird die Variable nur gebraucht, sollte der Puffer von PLinkName zu klein für den Linknamen, sie enthält dann den Wert für die notwendige Größe. Merkwürdig ist nur, dass es bei einer erneuten Zuweisung nach ShowMessage(PWideChar(@PLinkName)); knallt. Im Debugger sieht man, dass der Wert von PLinkLänge^ nach Aufruf von FindFirstFileNameW plötzlich nicht mehr verfügbar ist. Und leider ebenfalls unverständlich ist mir, dass die Sache mit ERROR_MORE_DATA nicht funktioniert, wenn ich den Puffer von PLinkName absichtlich zu klein bemesse (z.B. PLinkName := GetMemory(2); , obwohl das nicht sein dürfte und bei meinen früheren Versuchen auch korrekt ERROR_MORE_DATA zurückgegeben wurde. Klappen tut die Sache trotzdem! Und was passiert da nun?

Noch eine kleine Bemerkung zu "4294967295" statt INVALID_HANDLE_VALUE. Im Entwurfsmodus hat INVALID_HANDLE_VALUE den Wert -1, wird aber dennoch treuherzig als "Cardinal" deklariert. Zur Laufzeit wechselt der Wert im Debugger dann zu den besagten 4294967295, was schon sehr irritierend ist. Ein Ausflug in die Welt von DWORD bei Windows erklärt dann so einiges. Bei mir hat der Compiler in einigen Fällen (nicht in allen ?!) darauf hingewiesen, dass If Handle = INVALID_HANDLE_VALUE immer "Falsch" ergibt - logisch bei Cardinal und einem Wert von -1 zur Entwufszeit.

Für weitere Erhellung des Codes oben wäre ich dankbar.

Geändert von Benmik (12. Jan 2014 um 16:56 Uhr) Grund: Sprachliche Glättung
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#3

AW: FindFirstFileNameW aufrufen

  Alt 12. Jan 2014, 19:50
Was mir grade spontan auffällt ist, dass du den Funktionsprototypen falsch nach Delphi portiert hast.
function FindNextFileNameW(Handle:Cardinal;var DatLänge:PDWORD;var LinkName:PWChar): Boolean; sollte eher das hier sein:
function FindNextFileNameW(hFindStream: THandle; var StringLength: DWord; LinkName: PWChar): Boolean; "var PDWord" und "var PWChar" ist doppelt. Nimm hier entweder einen var Parameter ODER einen Zeigerdatentyp

Edit 1: Hier mal mein Ansatz:
Delphi-Quellcode:
var
  FindFirstFileNameW: function(lpFileName: LPCWSTR; dwFlags: DWord; var StringLength: DWord;
    LinkName: PWChar): THandle; stdcall;
  FindNextFileNameW: function(hFindStream: THandle; var StringLength: DWord;
    LinkName: PWChar): BOOL; stdcall;
var
  LinkName: array[0..MAX_PATH - 1] of WideChar;
  StringLength: DWord;
  hFindStream: THandle;
begin
  @FindFirstFileNameW := GetProcAddress(LoadLibrary('kernel32.dll'), 'FindFirstFileNameW');
  @FindNextFileNameW := GetProcAddress(LoadLibrary('kernel32.dll'), 'FindNextFileNameW');
  if (not Assigned(FindFirstFileNameW)) or (not Assigned(FindNextFileNameW)) then
  begin
    raise Exception.Create('Fehlermeldung');
  end;
  StringLength := MAX_PATH - 1;
  hFindStream := FindFirstFileNameW('D:\Bilder\Avatare\Mitch.png', 0, StringLength, @LinkName[0]);
  if (hFindStream = INVALID_HANDLE_VALUE) then RaiseLastOSError;
  try
    repeat
      ShowMessage(StrPas(PWideChar(@LinkName[0])));
      StringLength := MAX_PATH - 1;
    until (not FindNextFileNameW(hFindStream, StringLength, @LinkName[0]));
  finally
    Winapi.Windows.FindClose(hFindStream);
  end;
end;
Edit 2:
INVALID_HANDLE_VALUE zu verwenden ist definitiv sicher. Dieser hohe Wert 429.. entspricht in Binärdarstellung einfach nur 4 Bytes, bei denen sämtliche Bits auf 1 gesetzt sind. Ob man die Zahl nun unsigned als 429.. darstellt, oder per DWord(-1) oder auch $FFFFFFFF ist im Grunde egal. Durch den Cast auf DWord, wird das höchste Bit nun einfach nicht mehr als Vorzeichen in Zweierkomplement-Darstellung interpretiert, sondern als zusätzliche 2er Potenz 2^31 aufaddiert.
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)

Geändert von Zacherl (12. Jan 2014 um 20:09 Uhr)
  Mit Zitat antworten Zitat
Benmik

Registriert seit: 11. Apr 2009
570 Beiträge
 
Delphi 12 Athens
 
#4

AW: FindFirstFileNameW aufrufen

  Alt 12. Jan 2014, 21:15
Sensationell. Das Ding funktioniert ohne Wenn und Aber. (Nur das "Winapi" musste ich entfernen und dieses "Mitch.png" konnte ich einfach nirgendwo finden... ) Toll. Danke.

Ein paar Fragen hätte ich noch.

Sollte man das StrPas umgehen, da es als veraltet bezeichnet wird?

Wieso funktioniert eigentlich die Übergabe von @LinkName[0], ohne dass der Compiler die formale Typdeklaration moniert? (Auf @LinkName[0] muss man auch erstmal kommen).
Wieso StringLength := MAX_PATH - 1 ?

Nochmal danke.
  Mit Zitat antworten Zitat
Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
44.340 Beiträge
 
Delphi 12 Athens
 
#5

AW: FindFirstFileNameW aufrufen

  Alt 12. Jan 2014, 21:59
Zitat:
MAX_PATH - 1
Da muß man aufßassen, also wie die Angabe der Buffer-Größeangegeben werden muß.

- in Bytes
- in Chars

- mit abschließendem #0
- ohne abschließendem #0

usw.



In Chars ohne #0 muß der Puffer 1 Zeichen größer sein, da die Funktion noch ein #0 anhängt.


Zitat:
@LinkName[0]
Natürlich nur bei dem Array, welches bei 0 anfängt,
ansonsten 1, da Strings bei 1 beginnen,
außer beim neuen mobilen Compiler, wo man nun auch Strings urplötzlich mit 0 anfangen läßt.
Indize auf einen PChar fangen allerdings auch bei 0 an, da diese ein Char-Array darstellen.
Ein Therapeut entspricht 1024 Gigapeut.
  Mit Zitat antworten Zitat
Benutzerbild von Zacherl
Zacherl

Registriert seit: 3. Sep 2004
4.629 Beiträge
 
Delphi 10.2 Tokyo Starter
 
#6

AW: FindFirstFileNameW aufrufen

  Alt 13. Jan 2014, 07:52
Sollte man das StrPas umgehen, da es als veraltet bezeichnet wird?
Das kann ich dir gar nicht genau sagen. Theoretisch kannst du an dieser Stelle auch einfach nur PWideChar(@LinkName[0]) schreiben denke ich.

Wieso funktioniert eigentlich die Übergabe von @LinkName[0], ohne dass der Compiler die formale Typdeklaration moniert? (Auf @LinkName[0] muss man auch erstmal kommen)
Wenn du einen Pointer Typen an eine Funktion übergibst, es es dem Compiler vollkommen egal, ob du jetzt ein PDWord, ein PInt64 oder einen einen PWideChar (@LinkName[0]) verwendest. Da musst du selbst dafür sorgen, dass die Logik korrekt ist. Du hattest LinkName im Funktionsprototypen ursprünglich als var Parameter deklariert. Intern ist das eigentlich nur syntaktischer Zucker für Pointer, die als inout Parameter genutzt werden, aber in der Praxis kontrolliert Delphi hier ganz penibel die Typen, die du übergibst.
Projekte:
- GitHub (Profil, zyantific)
- zYan Disassembler Engine ( Zydis Online, Zydis GitHub)
  Mit Zitat antworten Zitat
Benmik

Registriert seit: 11. Apr 2009
570 Beiträge
 
Delphi 12 Athens
 
#7

AW: FindFirstFileNameW aufrufen

  Alt 13. Jan 2014, 18:15
Theoretisch kannst du an dieser Stelle auch einfach nur PWideChar(@LinkName[0]) schreiben denke ich. Kann man!

Dass Pointer UND var Unsinn sind, darauf war ich auch schon gestoßen. Bei Pointern muss man halt umdenken, da ist vermutlich viel Gewöhnungssache dabei. Das ist etwas neben deiner - übrigens auch recht eleganten - Lösung etwas, was das stundenlange Herumpfriemeln mir gebracht hat.
  Mit Zitat antworten Zitat
Antwort Antwort

 

Forumregeln

Es ist dir nicht erlaubt, neue Themen zu verfassen.
Es ist dir nicht erlaubt, auf Beiträge zu antworten.
Es ist dir nicht erlaubt, Anhänge hochzuladen.
Es ist dir nicht erlaubt, deine Beiträge zu bearbeiten.

BB-Code ist an.
Smileys sind an.
[IMG] Code ist an.
HTML-Code ist aus.
Trackbacks are an
Pingbacks are an
Refbacks are aus

Gehe zu:

Impressum · AGB · Datenschutz · Nach oben
Alle Zeitangaben in WEZ +1. Es ist jetzt 16:53 Uhr.
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024-2025 by Thomas Breitkreuz