Delphi-PRAXiS
Seite 1 von 2  1 2   

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Win32/Win64 API (native code) (https://www.delphipraxis.net/17-win32-win64-api-native-code/)
-   -   Delphi FindFirstFileNameW aufrufen (https://www.delphipraxis.net/178502-findfirstfilenamew-aufrufen.html)

Benmik 11. Jan 2014 14:43

FindFirstFileNameW aufrufen
 
Für ein geplantes selbst gestricktes Backup-Programm möchte ich gern bei Hardlinks feststellen, welche Dateinamen alle auf den gleichen Speicherbereich ("Datei") zeigen. Nach viel Recherche bin ich darauf gekommen, dass hier FindFirstFileNameW und FindNextFileNameW genau das Richtige sind.
Die Windows-API definiert FindFirstFileNameW(_In_ LPCWSTR lpFileName,_In_ DWORD dwFlags,_Inout_ LPDWORD StringLength,_Inout_ PWCHAR LinkName) und FindNextFileNameW(_In_ HANDLE hFindStream,_Inout_ LPDWORD StringLength,_Inout_ PWCHAR LinkName).
Trotz des Anspruchs, dass Delphi die gesamte Windows-API kapselt, habe ich nichts dergleichen gefunden.
Erstaunlicherweise ergibt die Google-Suchen nach den Funktionen im Zusammenhang mit Delphi NIX, GAR NIX !
Ich müsste also die Funktionen dynamisch aus kernel32 laden.
Hierzu habe ich für GetTickCount64 ein Beispiel gefunden:
Delphi-Quellcode:
function GetTickCount64: Int64;

implementation

uses Windows, SysUtils;

type
   // Don't forget stdcall for API functions.
  TGetTickCount64 = function: Int64; stdcall;

var
  _GetTickCount64: TGetTickCount64;

// Load the Vista function if available, and call it.
// Raise EOSError if the function isn't available.
function GetTickCount64: Int64;
var
  kernel32: HModule;
begin
  if not Assigned(_GetTickCount64) then begin
    // Kernel32 is always loaded already, so use GetModuleHandle
    // instead of LoadLibrary
    kernel32 := GetModuleHandle('kernel32');
    if kernel32 = 0 then
      RaiseLastOSError;
    @_GetTickCount := GetProcAddress(kernel32, 'GetTickCount64');
    if not Assigned(_GetTickCount64) then
      RaiseLastOSError;
  end;
  Result := _GetTickCount64;
end;
Weiß einer von den Delphi-Cracks hier vielleicht, wie die beiden Funktionen aufzurufen wären?

Sir Rufo 11. Jan 2014 15:02

AW: FindFirstFileNameW aufrufen
 
AFAIK wird mit Delphi-Referenz durchsuchenSysUtils.FindFirst Delphi-Referenz durchsuchenSysUtils.FindNext genau das gekapselt.
Das Ergebnis findest du im Delphi-Referenz durchsuchenSysUtils.TSearchRec

himitsu 11. Jan 2014 15:05

AW: FindFirstFileNameW aufrufen
 
Delphi-Referenz durchsuchenFindFirst kapselt MSDN-Library durchsuchenFindNextFile (FindNextFileA und ab D2009 FindNextFileW),
aber hier geht es um MSDN-Library durchsuchenFindNextFileNameW.

Das geht genauso, wie im Beisiel:
- Funktions-Typ deklarieren (entsprechend wie TGetTickCount64 die API-Deklaration übersetzen)
- über MSDN-Library durchsuchenLoadLibrary die DLL laden, oder via MSDN-Library durchsuchenGetModuleHandle das Handle holen, wenn die DLL definitiv schon geladen ist
- den Prozedur-Einsprungpunkt holen (MSDN-Library durchsuchenGetProcAddress) und der Variable des Funktionstyps zuweisen
(und dafür gibt es eigentlich millionen Beispiele und Tutorials zu finden, wie man eine API/WinAPI einbindet, auch wenn es kaum welcher für gerade diese API gibt, aber das Grundprinzip bleibt ja gleich)

Wenn du die Funktion unbedingt brauchst und das Programm ohne nicht geht, dann kann man die Funktion auch statisch einbinden ... siehe z.B. die Unit Windows, wo statische und dynamische Beispiele drin sind.


In deinem Beispiel wird die Funktion jedesmal bei jedem Aufruf geprüft und wenn noch nicht geladen, dann wird das erstmal gemacht, vor dem Aufruf.
Man kann die Funktions-Variable aber auch schon beim Programmstart (in einer eigenen Init-Prozedur, welche man aufruft) oder in Initialization laden.

Benmik 11. Jan 2014 16:18

AW: FindFirstFileNameW aufrufen
 
Vielen Dank!

Insgeheim hatte ich natürlich gehofft, einer von euch Könnern würde mal lässig was aus dem Handgelenk hier hin werfen, aber naja, Weihnachten ist vorbei, und hier wird ja viel Wert auf Eigenanstrengung gelegt ...
Ehrlich gesagt ist es nicht so sehr die Anpassung des Beispiels, die mir, glaube ich, nicht soviel Probleme bereiten würde, als vielmehr die Übersetzung der Datentypen, da ich von C++ keine Ahnung habe. Was für ein Datentyp ist zum Beispiel "hFindStream"? Ein Handle, also Cardinal?

Aber da habe ich doch diese nette Seite gefunden, die mir vermutlich weiterhelfen wird. Das Konzept der Eigenanstrengung scheint also nicht verkehrt zu sein (wenn mir auch Weihnachten lieber ist...)

Benmik 11. Jan 2014 19:41

AW: FindFirstFileNameW aufrufen
 
Wie vermutet sind es die Datentypen, insbesondere die Pointer, bei denen ich nicht weiterkomme.

Könnte mir vielleicht doch jemand helfen, wie ich mit Typumwandlungen, der eventuellen Initialisierung und dem Aufruf von LPCWSTR, LPDWORD und PWCHAR verfahren muss? Die Funktion selber scheint zu klappen, aber der Rückgabewert von "LinkName" ist Müll.
Ich habe Linkname als PWideChar deklariert und mit GetMem(LinkName, 256 * SizeOf(WideChar)) initialisiert, StringLength als LPDWORD mit GetMem(PDatLänge, SizeOf(DWORD)). Aber das funktioniert nicht und beim Beenden gibt es trotz FreeMem eine Schutzverletzung.
Vielleicht könnte mir doch jemand unter die Arme greifen.

himitsu 11. Jan 2014 20:13

AW: FindFirstFileNameW aufrufen
 
Bei PChars wird oftmals nur der Inhalt überschrieben und nicht der Variablenzeiger.

Den Speicher für die Variable mußt du vorher reservieren.

GetMemory, SetLength oder sonswie, jenachdem was für einen Speicher du nimmst.

z.B. kann man sich einen WideString oder UnicodeString nehmen, da via SetLength ausreichend Speicher/Zeichen reservieren und das dann via PWideChar gegastet an die Funktion übergeben, welche dann den Text dort reinschreibt.

Benmik 11. Jan 2014 21:41

AW: FindFirstFileNameW aufrufen
 
So, nachdem ich wirklich heißgelaufen bin über vermutlich triviale Probleme, hier das (funktionierende) Resultat:
Delphi-Quellcode:
function FindNextFileNameW(Handle:Cardinal;var DatLänge:PDWORD;var LinkName:PWChar): Boolean;
type
  TFindNextFileNameW = function(Handle:Cardinal;var DatLänge:PDWORD;var LinkName:PWChar): Boolean; stdcall;
var
  kernel32: HModule;
  _TFindNextFileNameW: TFindNextFileNameW;
begin
  kernel32              := GetModuleHandle('kernel32');
  @_TFindNextFileNameW := GetProcAddress(kernel32, 'FindNextFileNameW');
  Result               := _TFindNextFileNameW(Handle,DatLänge,LinkName);
end;

function FindFirstFileNameW(Dateiname:PWideChar;Marker:Cardinal;var DatLänge:PDWORD;var LinkName:PWChar): Cardinal;
type
  TFindFirstFileNameW = function(Dateiname:PWideChar;Marker:Cardinal;var DatLänge:PDWORD;var LinkName:PWChar): Cardinal; stdcall;
var
  kernel32: HModule;
  _TFindFirstFileNameW: TFindFirstFileNameW;
begin
  kernel32              := GetModuleHandle('kernel32');
  @_TFindFirstFileNameW := GetProcAddress(kernel32, 'FindFirstFileNameW');
  Result               := _TFindFirstFileNameW(Dateiname,0,DatLänge,LinkName);
end;

function CloseHFile(Handle:Cardinal): Boolean;
type
  TCloseHFile = function(Handle:Cardinal): Boolean; stdcall;
var
  kernel32: HModule;
  _TCloseHFile: TCloseHFile;
begin
  kernel32      := GetModuleHandle('kernel32');
  @_TCloseHFile := GetProcAddress(kernel32, 'FindClose');
  Result       := _TCloseHFile(Handle);
end;

procedure TForm1.ZeigeHardlinks;
var
  Dateiname : WideString;
  DatLänge : Cardinal;
  PDatLänge : PDWORD;
  LinkName : WideChar;
  PLinkName : PWideChar;
  Handle   : Cardinal;
begin
  Dateiname := 'C:\Temp\Hardlink.JPG';
  PLinkName := @LinkName;
  PDatLänge := @DatLänge;
  Handle   := FindFirstFileNameW(PWideChar(Dateiname),0,PDatLänge,PLinkName);
  If Handle = 4294967295
    then Showmessage(SysErrorMessage(GetLastError));
  Showmessage(WideCharToString(@PLinkName));
  While FindNextFileNameW(Handle,PDatLänge,PLinkName) do
    Showmessage(WideCharToString(@PLinkName));
  CloseHFile(Handle);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  ZeigeHardlinks;
end;
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.

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?

himitsu 12. Jan 2014 00:13

AW: FindFirstFileNameW aufrufen
 
Zitat:

Zitat von Benmik (Beitrag 1243220)
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?

Zitat:

Zitat von Benmik (Beitrag 1243220)
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:

Delphi-Quellcode:
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.

Benmik 12. Jan 2014 16:26

AW: FindFirstFileNameW aufrufen
 
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
Delphi-Quellcode:
Handle := FindFirstFileNameW(PWideChar(Dateiname),0,@PDatLänge,@PLinkName);
moniert der Compiler, dass die Parameter formal nicht stimmen, genauso wie bei
Delphi-Quellcode:
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
Delphi-Quellcode:
FreeMemory(PLinkName);
knallt es aber. Schleierhaft, wieso.

Die Zuweisung
Delphi-Quellcode:
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
Delphi-Quellcode:
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.
Delphi-Quellcode:
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
Delphi-Quellcode:
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.

Zacherl 12. Jan 2014 19:50

AW: FindFirstFileNameW aufrufen
 
Was mir grade spontan auffällt ist, dass du den Funktionsprototypen falsch nach Delphi portiert hast.
Delphi-Quellcode:
function FindNextFileNameW(Handle:Cardinal;var DatLänge:PDWORD;var LinkName:PWChar): Boolean;
sollte eher das hier sein:
Delphi-Quellcode:
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.


Alle Zeitangaben in WEZ +1. Es ist jetzt 13:57 Uhr.
Seite 1 von 2  1 2   

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz