为什么ReadDirectoryChangesW省略事件? [英] Why does ReadDirectoryChangesW omit events?

查看:868
本文介绍了为什么ReadDirectoryChangesW省略事件?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  var 
InfoPointer:PFileNotifyInformation;
NextOffset:DWORD;
...
while(not Terminated)do begin
如果ReadDirectoryChangesW(FDirHandle,FBuffer,FBufferLength,True,
FFilter,@BytesRead,@FOverlap,nil)then
begin
WaitResult:= WaitForMultipleObjects(2,@FEventArray,False,INFINITE);
if(WaitResult = waitFileChange)then
begin
InfoPointer:= FBuffer;
重复
NextOffset:= InfoPointer.NextEntryOffset;
...
PByte(InfoPointer):= PByte(InfoPointer)+ NextOffset;
,直到NextOffset = 0;
结束
结束
结束

过滤器

 code> FFilter:= FILE_NOTIFY_CHANGE_FILE_NAME或
FILE_NOTIFY_CHANGE_DIR_NAME或
FILE_NOTIFY_CHANGE_SIZE或
FILE_NOTIFY_CHANGE_LAST_WRITE;

,目录句柄如下:



$ $ $ $ $ $ $ $ $ $ $ FD $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ FILE_FLAG_BACKUP_SEMANTICS或
FILE_FLAG_OVERLAPPED,0);

当我删除多个文件时,我只得到一个事件,NextOffset为0!当我删除一个目录时,我只得到一个目录的事件。如果我想为目录中的每个文件要一个事件怎么办?



任何帮助将不胜感激。

解决方案

在我看来,您正在混合使用 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;覆盖
public
构造函数Create(ADirectoryToWatch:string);
析构函数覆盖

程序关机;
结束

构造函数TWatcherThread.Create(ADirectoryToWatch:string);
const
FILE_LIST_DIRECTORY = 1;
begin
继承Create(TRUE);
fChangeHandle:= CreateEvent(nil,FALSE,FALSE,nil);
fDirHandle:= CreateFile(PChar(ADirectoryToWatch),
FILE_LIST_DIRECTORY或GENERIC_READ,
FILE_SHARE_READ或FILE_SHARE_WRITE或FILE_SHARE_DELETE,
nil,OPEN_EXISTING,FILE_FLAG_BACKUP_SEMANTICS或FILE_FLAG_OVERLAPPED,0);
fShutdownHandle:= CreateEvent(nil,FALSE,FALSE,nil);
简历;
结束

析构函数TWatcherThread.Destroy;
begin
如果fDirHandle<> INVALID_HANDLE_VALUE然后
CloseHandle(fDirHandle);
如果fChangeHandle<> 0然后
CloseHandle(fChangeHandle);
if fShutdownHandle<> 0然后
CloseHandle(fShutdownHandle);
继承了Destroy;
结束

程序TWatcherThread.Execute;
type
PFileNotifyInformation = ^ TFileNotifyInformation;
TFileNotifyInformation = record
NextEntryOffset:DWORD;
动作:DWORD;
FileNameLength:DWORD;
FileName:WideChar;
结束
const
BufferLength = 65536;
var
过滤器,BytesRead:DWORD;
InfoPointer:PFileNotifyInformation;
Offset,NextOffset:DWORD;
缓冲区:数组[0..BufferLength - 1]的字节;
重叠:TOverlapped;
事件:数组[0..1]的THandle;
WaitResult:DWORD;
FileName,s:string;
begin
如果fDirHandle<> INVALID_HANDLE_VALUE然后开始
过滤器:= FILE_NOTIFY_CHANGE_FILE_NAME或FILE_NOTIFY_CHANGE_DIR_NAME
或FILE_NOTIFY_CHANGE_SIZE或FILE_NOTIFY_CHANGE_LAST_WRITE;

FillChar(重叠,SizeOf(TOverlapped),0);
Overlap.hEvent:= fChangeHandle;

事件[0]:= fChangeHandle;
事件[1]:= fShutdownHandle;如果ReadDirectoryChangesW(fDirHandle,@Buffer [0],BufferLength,TRUE,
Filter,@BytesRead,@Overlap,nil)
,则

然后开始
WaitResult:= WaitForMultipleObjects(2,@Events [0],FALSE,INFINITE);
如果WaitResult = WAIT_OBJECT_0然后开始
InfoPointer:= @Buffer [0];
偏移:= 0;
重复
NextOffset:= InfoPointer.NextEntryOffset;
FileName:= WideCharLenToString(@ InfoPointer.FileName,
InfoPointer.FileNameLength);
SetLength(FileName,StrLen(PChar(FileName)));
s:=格式('[%d]动作:%.8xh,文件:%s',
[Offset,InfoPointer.Action,FileName]);
OutputDebugString(PChar(s));
PByte(InfoPointer):= PByte(DWORD(InfoPointer)+ NextOffset);
Offset:= Offset + NextOffset;
,直到NextOffset = 0;
结束
结束
结束
结束
结束

程序TWatcherThread.Shutdown;
begin
终止;
if fShutdownHandle<> 0 then
SetEvent(fShutdownHandle);
结束

//////////////////////////////////////// ////////////////////

程序TForm1.FormCreate (发件人:TObject);
begin
fThread:= TWatcherThread.Create('D:\Temp');
结束

procedure TForm1.FormDestroy(Sender:TObject);
begin
如果fThread<>然后开始
TWatcherThread(fThread).Shutdown;
fThread.Free;
结束
结束

删除目录确实只会返回一个更改,对其中包含的文件无任何影响。但它确实有意义,因为您正在仅监视父目录的句柄。如果您需要子目录的通知,您可能还需要观看它们。


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.

解决方案

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.

这篇关于为什么ReadDirectoryChangesW省略事件?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆