为什么ReadDirectoryChangesW会忽略事件?

我使用ReadDirectoryChangesW来监视一个指定的目录,并在检测到更改时更新索引结构。 我使用下面的代码(大致)

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; 

和目录句柄是这样获得的:

 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); 

当我删除多个文件,我只得到一个事件,NextOffset是0! 而当我删除一个目录,我只得到一个目录的事件。 如果我想要在目录中的每个文件的一个事件呢?

任何帮助,将不胜感激。

Solutions Collecting From Web of "为什么ReadDirectoryChangesW会忽略事件?"

在我看来,您正在混合使用ReadDirectoryChangesW()的各种方法,您在打开目录时都指定FILE_FLAG_OVERLAPPED标志,并提供一个指向lpOverlapped参数的指针,这意味着您要等待结构中的事件并处理异步I / O; 同时在工作线程的循环中调用ReadDirectoryChangesW() 我会再次尝试使用lpOverlapped设置为零 ,因为你有一个专用的线程,可以使用同步模式。

ReadDirectoryChangesW() API函数的文档中描述了使用它的不同方式。 请注意,缓冲区溢出也是可能的,所以更改事件可能会丢失。 也许你应该重新思考你单纯依靠这个功能的策略,比较目录内容的快照也可以。

编辑:

你编辑的代码看起来更好。 但是在我的测试中, ReadDirectoryChangesW()确实按照广告方式工作,在返回的缓冲区中有几个数据项,或者有多个缓冲区需要处理。 这取决于时间,在Delphi中打断点后,我得到了一个缓冲区中的几个条目。

为了完整起见,我附上了使用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; 

删除一个目录确实只会返回一个改变,对于其中包含的文件没有任何改变。 但它是有道理的,因为你只看到父目录的句柄。 如果您需要子目录的通知,您可能还需要观看它们。

我们也遇到了同样的问题,尤其是在同时发生很多变化的情况下。 将500个文件复制到受监视的目录。

最后我们找到了Cromis并使用了Directory手表 。 我们再也没有回头看过。