9

Io uso ReadDirectoryChangesW per guardare una directory specificata e aggiornare le strutture di indicizzazione ogni volta che viene rilevata una modifica. Io uso il seguente codice (circa)Perché ReadDirectoryChangesW omette gli eventi?

var 
    InfoPointer : PFileNotifyInformation; 
    NextOffset : DWORD; 
... 
while (not Terminated) do begin 
    if ReadDirectoryChangesW (FDirHandle, FBuffer, FBufferLength, True, 
          FFilter, @BytesRead, @FOverlap, nil) then 
    begin 
    WaitResult := WaitForMultipleObjects (2, @FEventArray, False, INFINITE); 
    if (WaitResult = waitFileChange) then 
     begin 
     InfoPointer := FBuffer; 
     repeat 
     NextOffset := InfoPointer.NextEntryOffset; 
     ... 
     PByte (InfoPointer) := PByte (InfoPointer) + NextOffset; 
     until NextOffset = 0; 
     end; 
    end; 
end; 

Filter è

FFilter := FILE_NOTIFY_CHANGE_FILE_NAME or 
      FILE_NOTIFY_CHANGE_DIR_NAME or 
      FILE_NOTIFY_CHANGE_SIZE or 
      FILE_NOTIFY_CHANGE_LAST_WRITE; 

e la maniglia di directory si ottiene in questo modo:

FDirHandle := CreateFile (PChar (FDirectoryWatch.WatchedDirectory), 
          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);   

Quando ho eliminare più file ottengo un solo evento e NextOffset è 0! E quando elimino una directory ottengo solo un evento per la directory. Cosa succede se voglio un evento per ogni file nella directory?

Qualsiasi aiuto sarebbe apprezzato.

risposta

15

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.

+0

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

+0

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

+1

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

4

Abbiamo avuto lo stesso problema con la perdita di eventi, soprattutto se un sacco di cambiamenti si verificano allo stesso tempo, vale a dire. 500 file vengono copiati nella directory monitorata.

Alla fine abbiamo trovato Cromis e utilizzare Directory watch. Non ci siamo mai voltati indietro.

+0

directory watch è davvero buono. per la compatibilità a 64 bit devi sostituire getWindowLong con getWindowLongPtr (anche per set ...) – Ampere

Problemi correlati