为什么 ReadDirectoryChangesW 忽略事件?

发布于 2024-07-20 07:28:49 字数 1442 浏览 9 评论 0原文

我使用 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 is

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! 当我删除一个目录时,我只得到该目录的一个事件。 如果我希望目录中的每个文件都有一个事件怎么办?

任何帮助,将不胜感激。

I use ReadDirectoryChangesW to watch a specified directory and update indexing structures whenever a change is detected. I use the following code (roughly)

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 is

FFilter :=  FILE_NOTIFY_CHANGE_FILE_NAME or
            FILE_NOTIFY_CHANGE_DIR_NAME or
            FILE_NOTIFY_CHANGE_SIZE or
            FILE_NOTIFY_CHANGE_LAST_WRITE;

and the directory handle is obtained like this:

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

When I delete multiple files I get only one event and NextOffset is 0! And when I delete a directory I get only one event for the directory. What if I want one event for each file in the directory?

Any help would be appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不一样的天空 2024-07-27 07:28:49

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

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;

删除目录确实只返回一项更改,而不会返回其中包含的文件。 但这确实有意义,因为您只查看父目录的句柄。 如果您需要子目录的通知,您可能还需要观看它们。

It seems to me that you are mixing the various ways to use ReadDirectoryChangesW(), you do both specify the FILE_FLAG_OVERLAPPED flag when opening the directory and provide a pointer to the lpOverlapped parameter, meaning you want to wait on the event in the structure and handle the asynchronous I/O; and at the same time you call ReadDirectoryChangesW() in a loop in a worker thread. I would first try again with lpOverlapped set to nil, as you have a dedicated thread and can use the synchronous mode.

In the documentation of the ReadDirectoryChangesW() API function the different ways to use it are described. Note that it is also possible that the buffer overflows, so change events can be lost anyway. Maybe you should rethink your strategy of relying solely on this function, comparing snapshots of directory contents could work as well.

Edit:

Your edited code looks better. In my tests however ReadDirectoryChangesW() did work as advertised, there were either several data entries in the returned buffer, or there were more than one buffer to process. This depends on timing, after hitting a breakpoint in Delphi I get several entries in one buffer.

For completeness I attach the test code, implemented using 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;

Deleting a directory does indeed only return one change for it, nothing for the files contained in it. But it does make sense, as you are watching the handle of the parent directory only. If you need notifications for subdirectories you probably need to watch them as well.

静若繁花 2024-07-27 07:28:49

我们在丢失事件方面也遇到了同样的问题,特别是如果同时发生很多变化,即。 500 个文件被复制到受监控的目录。

最后我们找到了 Cromis 并使用 目录监视。 我们再也没有回头。

We've had the same problem with losing events, especially if a lot of changes happens at the same time, ie. 500 files are copied to the monitored directory.

In the end we found Cromis and use the Directory watch. We have never looked back again.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文