Windows 为什么ReadDirectoryChangesW忽略事件?

Windows 为什么ReadDirectoryChangesW忽略事件?,windows,delphi,filesystems,delphi-2009,readdirectorychangesw,Windows,Delphi,Filesystems,Delphi 2009,Readdirectorychangesw,我使用ReadDirectoryChangesW监视指定的目录,并在检测到更改时更新索引结构。我使用以下代码(大致) 过滤器是 FFilter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME or FILE_NOTIFY_CHANGE_SIZE or FILE_NOTIFY_CHANGE_LAST_WRITE; 目录句柄如下所示: FDirHa

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

过滤器是

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


任何帮助都将不胜感激。

在我看来,您正在混合使用ReadDirectoryChangesW()的各种方法,您在打开目录时指定文件\u标志\u重叠标志,并提供指向lpOverlapped参数的指针,这意味着您希望在结构中等待事件并处理异步I/O;同时,在工作线程的循环中调用ReadDirectoryChangesW()。我首先将lpOverlapped设置为nil,然后再试一次,因为您有一个专用线程,可以使用同步模式

在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;
类型
TWatcherThread=类(TThread)
私有的
fchangehold:THandle;
fDirHandle:THandle;
fshuttownhandle:THandle;
受保护的
程序执行;推翻
公众的
构造函数创建(ADirectoryToWatch:string);
毁灭者毁灭;推翻
程序关闭;
结束;
构造函数TWatcherThread.Create(ADirectoryToWatch:string);
常数
文件\列表\目录=1;
开始
继承创建(TRUE);
fChangeHandle:=CreateEvent(nil,FALSE,FALSE,nil);
fDirHandle:=CreateFile(PChar(ADirectoryToWatch),
文件\u列表\u目录或通用\u读取,
文件共享读取或文件共享写入或文件共享删除,
nil,OPEN\u EXISTING,FILE\u FLAG\u BACKUP\u语义或FILE\u FLAG\u重叠,0);
fShutdownHandle:=CreateEvent(nil,FALSE,FALSE,nil);
简历
结束;
析构函数TWatcherThread.销毁;
开始
如果fDirHandle的值无效,则
闭合手柄(fDirHandle);
如果fChangeHandle为0,则
关闭手柄(fChangeHandle);
如果fShutdownHandle为0,则
关闭手柄(FSHUTTONSHADLE);
继承性破坏;
结束;
过程TWatcherThread.Execute;
类型
PFileNotifyInformation=^TFileNotifyInformation;
TFileNotifyInformation=记录
下一个偏移量:DWORD;
行动:德沃德;
文件名长度:DWORD;
文件名:WideChar;
结束;
常数
缓冲长度=65536;
变量
过滤器,字节读:DWORD;
InfoPointer:PFileNotifyInformation;
偏移量,NEXTOFSET:DWORD;
缓冲区:字节的数组[0..BufferLength-1];
重叠:重叠;
事件:THandle的数组[0..1];
结果:德沃德;
文件名,s:string;
开始
如果fDirHandle的句柄值无效,则开始
过滤器:=文件\通知\更改\文件\名称或文件\通知\更改\目录\名称
或文件通知更改大小或文件通知更改上次写入;
FillChar(重叠,SizeOf(TOverlapped),0);
Overlap.hEvent:=fchangeholder;
事件[0]:=fchangehold;
事件[1]:=fShutdownHandle;
虽然没有终止,但不要开始
如果ReadDirectoryChangesW(fDirHandle,@Buffer[0],BufferLength,TRUE,
过滤器,@BytesRead,@Overlap,nil)
然后开始
WaitResult:=WaitForMultipleObjects(2,@Events[0],FALSE,无限);
如果WaitResult=WAIT_OBJECT_0,则开始
InfoPointer:=@缓冲区[0];
偏移量:=0;
重复
nextofset:=InfoPointer.NextEntryOffset;
FileName:=WideCharLenToString(@InfoPointer.FileName,
InfoPointer.FileNameLength);
SetLength(文件名,StrLen(PChar(文件名));
s:=格式(“[%d]操作:%.8xh,文件:“%s”,
[偏移量,InfoPointer.Action,文件名];
OutputDebugString(PChar(s));
PByte(InfoPointer):=PByte(DWORD(InfoPointer)+nextofset);
偏移量:=偏移量+nextofset;
直到nextofset=0;
结束;
结束;
结束;
结束;
结束;
程序TWatcherThread.关闭;
开始
终止
如果fShutdownHandle为0,则
SetEvent(fShutdownHandle);
结束;
////////////////////////////////////////////////////////////////////////////////
过程TForm1.FormCreate(发送方:TObject);
开始
fThread:=TWatcherThread.Create('D:\Temp');
结束;
程序TForm1.FormDestroy(发送方:ToObject);
开始
如果fThread为nil,则开始
TWatcherThread(fThread).关闭;
fThread.Free;
结束;
结束;

删除一个目录实际上只会返回一个更改,其中包含的文件不会返回任何更改。但这是有意义的,因为您只关注父目录的句柄。如果您需要子目录的通知,您可能还需要监视它们。

我们也遇到了丢失事件的问题,特别是当同时发生大量更改时,即.500个文件被复制到受监视的目录


最后我们找到并使用了。我们再也没有回首往事。

很抱歉我的评论被耽搁了。我以前使用过同步版本(有着完全相同的问题),但后来改用了异步版本,因为我没有找到干净地终止线程的方法。不过,我在示例代码中遗漏了一行重要的代码(对WaitForMultipleObjects的阻塞调用,它可以由文件更改事件或终止事件终止)。
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;