Mi sembra che si stia mescolando i vari modi di utilizzare ReadDirectoryChangesW(), non sia specificato il FILE _ FLAG _ INGAGGIATE bandiera quando si apre la directory e di fornire un puntatore al lpOverlapped parametro , nel senso che si desidera attendere l'evento nella struttura e gestire l'I/O asincrono; e allo stesso tempo si chiama ReadDirectoryChangesW() in un ciclo in un thread di lavoro. Vorrei innanzitutto riprovare con lpSuperchiato impostato su nil, poiché si dispone di un thread dedicato e può utilizzare la modalità sincrona.
Nella documentazione della funzione API ReadDirectoryChangesW() vengono descritti i diversi modi di utilizzarlo. Si noti che è anche possibile che il buffer trabocchi, quindi gli eventi di modifica possono essere persi comunque. Forse dovresti riconsiderare la tua strategia di affidarti esclusivamente a questa funzione, confrontando anche le istantanee dei contenuti delle directory.
Edit:
tuo codice modificato un aspetto migliore. Tuttavia, nei miei test ReadDirectoryChangesW() funzionava come pubblicizzato, c'erano diverse voci di dati nel buffer restituito o c'erano più di un buffer da elaborare. Questo dipende dal tempo, dopo aver colpito un punto di interruzione in Delphi ottengo diverse voci in un buffer.
Per completezza allego il codice di prova, implementato usando 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;
eliminazione di una directory non effettivamente restituire solo un cambiamento per esso, nulla per i file in essa contenuti. Ma ha senso, visto che stai guardando solo l'handle della directory padre. Se hai bisogno di notifiche per le sottodirectory probabilmente devi anche guardarle.
Ci scusiamo per il mio commento posticipato.Ho usato la versione sincrona prima (con gli stessi identici problemi) ma passata alla versione asincrona perché non ho trovato un modo per terminare in modo pulito il thread. Ho perso una riga importante nel codice di esempio (la chiamata di blocco a WaitForMultipleObjects, che può essere terminata da un evento di modifica file o da un evento di chiusura). Modifico la domanda di conseguenza. (...) – jpfollenius
Cosa intendi per istantanea? Se intendi il iterare su tutti i file utilizzando FindFirst, FindNext: in precedenza ho utilizzato un approccio simile ma volevo evitare (1) i tempi di rilevamento delle modifiche ritardate quando si utilizzavano directory di grandi dimensioni e (2) carico I/O costante per il thread di indicizzazione che rallentava tutto altre operazioni di I/O. – jpfollenius
Concordato con il vostro secondo commento, ma come afferma la documentazione MSDN è necessario essere pronti per gli overflow del buffer interno, e in tal caso è necessaria una (ri) scansione completa della directory. – mghie