Es scheint mir, dass Sie die verschiedenen Möglichkeiten, mischen ReadDirectoryChangesW() verwenden, Sie geben sowohl die FILE _ FLAG _ Flagge ÜBERSCHNEIDUNG wenn das Verzeichnis zu öffnen und einen Zeiger bereitzustellen, um die lpOverlapped Parameter , dh Sie möchten auf das Ereignis in der Struktur warten und die asynchrone E/A verarbeiten; und zur gleichen Zeit rufen Sie ReadDirectoryChangesW() in einer Schleife in einem Worker-Thread. Ich würde zuerst wieder versuchen mit lpOverlapped auf nil eingestellt, da Sie einen dedizierten Thread haben und den synchronen Modus verwenden können. In der Dokumentation der API-Funktion ReadDirectoryChangesW() werden die verschiedenen Verwendungsmöglichkeiten beschrieben. Beachten Sie, dass es auch möglich ist, dass der Puffer überläuft, sodass Änderungsereignisse trotzdem verloren gehen können. Vielleicht sollten Sie Ihre Strategie, sich ausschließlich auf diese Funktion zu verlassen, überdenken, und der Vergleich von Snapshots von Verzeichnisinhalten könnte ebenfalls funktionieren.
Edit:
bearbeiteten Code sieht besser aus. In meinen Tests jedoch ReadDirectoryChangesW() funktionierte wie angekündigt, es gab entweder mehrere Dateneinträge im zurückgegebenen Puffer, oder es gab mehr als einen Puffer zu verarbeiten. Das hängt vom Timing ab, nachdem ich einen Haltepunkt in Delphi getroffen habe, bekomme ich mehrere Einträge in einem Puffer.
Der Vollständigkeit ich den Test-Code anhängen, umgesetzt mit Delphi 5:
type
TWatcherThread = class(TThread)
private
fChangeHandle: THandle;
fDirHandle: THandle;
fShutdownHandle: THandle;
protected
procedure Execute; override;
public
constructor Create(ADirectoryToWatch: string);
destructor Destroy; override;
procedure Shutdown;
end;
constructor TWatcherThread.Create(ADirectoryToWatch: string);
const
FILE_LIST_DIRECTORY = 1;
begin
inherited Create(TRUE);
fChangeHandle := CreateEvent(nil, FALSE, FALSE, nil);
fDirHandle := CreateFile(PChar(ADirectoryToWatch),
FILE_LIST_DIRECTORY or GENERIC_READ,
FILE_SHARE_READ or FILE_SHARE_WRITE or FILE_SHARE_DELETE,
nil, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED, 0);
fShutdownHandle := CreateEvent(nil, FALSE, FALSE, nil);
Resume;
end;
destructor TWatcherThread.Destroy;
begin
if fDirHandle <> INVALID_HANDLE_VALUE then
CloseHandle(fDirHandle);
if fChangeHandle <> 0 then
CloseHandle(fChangeHandle);
if fShutdownHandle <> 0 then
CloseHandle(fShutdownHandle);
inherited Destroy;
end;
procedure TWatcherThread.Execute;
type
PFileNotifyInformation = ^TFileNotifyInformation;
TFileNotifyInformation = record
NextEntryOffset: DWORD;
Action: DWORD;
FileNameLength: DWORD;
FileName: WideChar;
end;
const
BufferLength = 65536;
var
Filter, BytesRead: DWORD;
InfoPointer: PFileNotifyInformation;
Offset, NextOffset: DWORD;
Buffer: array[0..BufferLength - 1] of byte;
Overlap: TOverlapped;
Events: array[0..1] of THandle;
WaitResult: DWORD;
FileName, s: string;
begin
if fDirHandle <> INVALID_HANDLE_VALUE then begin
Filter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME
or FILE_NOTIFY_CHANGE_SIZE or FILE_NOTIFY_CHANGE_LAST_WRITE;
FillChar(Overlap, SizeOf(TOverlapped), 0);
Overlap.hEvent := fChangeHandle;
Events[0] := fChangeHandle;
Events[1] := fShutdownHandle;
while not Terminated do begin
if ReadDirectoryChangesW (fDirHandle, @Buffer[0], BufferLength, TRUE,
Filter, @BytesRead, @Overlap, nil)
then begin
WaitResult := WaitForMultipleObjects(2, @Events[0], FALSE, INFINITE);
if WaitResult = WAIT_OBJECT_0 then begin
InfoPointer := @Buffer[0];
Offset := 0;
repeat
NextOffset := InfoPointer.NextEntryOffset;
FileName := WideCharLenToString(@InfoPointer.FileName,
InfoPointer.FileNameLength);
SetLength(FileName, StrLen(PChar(FileName)));
s := Format('[%d] Action: %.8xh, File: "%s"',
[Offset, InfoPointer.Action, FileName]);
OutputDebugString(PChar(s));
PByte(InfoPointer) := PByte(DWORD(InfoPointer) + NextOffset);
Offset := Offset + NextOffset;
until NextOffset = 0;
end;
end;
end;
end;
end;
procedure TWatcherThread.Shutdown;
begin
Terminate;
if fShutdownHandle <> 0 then
SetEvent(fShutdownHandle);
end;
////////////////////////////////////////////////////////////////////////////////
procedure TForm1.FormCreate(Sender: TObject);
begin
fThread := TWatcherThread.Create('D:\Temp');
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
if fThread <> nil then begin
TWatcherThread(fThread).Shutdown;
fThread.Free;
end;
end;
Verzeichnis löschen in der Tat nicht nur eine Änderung Rückkehr für sie, nichts für die darin enthaltenen Dateien. Aber es macht Sinn, da Sie nur den Handle des übergeordneten Verzeichnisses beobachten. Wenn Sie Benachrichtigungen für Unterverzeichnisse benötigen, müssen Sie sie wahrscheinlich ebenfalls ansehen.
Entschuldigung für meinen verspäteten Kommentar.Ich habe vorher die synchrone Version benutzt (mit genau den gleichen Problemen), bin aber zur asynchronen Version gewechselt, weil ich keine Möglichkeit gefunden habe, den Thread sauber zu beenden. Ich verpasste jedoch eine wichtige Zeile im Beispielcode (der blockierende Aufruf von WaitForMultipleObjects, der entweder durch ein Dateiänderungsereignis oder durch ein Terminierungsereignis terminiert werden kann). Ich bearbeite die Frage entsprechend. (...) – jpfollenius
Was meinst du mit Snapshot? Wenn Sie das Iterieren über alle Dateien mit FindFirst, FindNext meinen: Früher habe ich einen solchen Ansatz verwendet, aber ich wollte (1) verzögerte Änderungserkennungszeiten vermeiden, wenn große Verzeichnisse verwendet werden und (2) konstante I/O-Last für den Indizierungsthread alles verlangsamt andere E/A-Vorgänge. – jpfollenius
Vereinbart mit Ihrem zweiten Kommentar, aber wie die MSDN-Dokumentation besagt, müssen Sie auf Überläufe des internen Puffers vorbereitet sein, und in diesem Fall ist ein vollständiger (Neu-) Scan des Verzeichnisses erforderlich. – mghie