9

ReadDirectoryChangesWを使用して、指定されたディレクトリを監視し、変更が検出されるたびにインデックス構造を更新します。私は複数のファイルを削除するとなぜReadDirectoryChangesWはイベントを省略しますか?

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

私は唯一つのイベントを取得します:私は、次のコードは、(おおよそ)

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; 

フィルター

FFilter := FILE_NOTIFY_CHANGE_FILE_NAME or 
      FILE_NOTIFY_CHANGE_DIR_NAME or 
      FILE_NOTIFY_CHANGE_SIZE or 
      FILE_NOTIFY_CHANGE_LAST_WRITE; 

やディレクトリハンドルであるが、このように得られ、使用しますNextOffsetは0です!ディレクトリを削除すると、そのディレクトリのイベントは1つだけになります。ディレクトリ内のファイルごとに1つのイベントが必要な場合はどうなりますか?

ご協力いただければ幸いです。

答えて

15

あなたがReadDirectoryChangesW()を使用するさまざまな方法を混合しているように私には思える、あなたがFILE _ FLAGを指定すればよいの両方_はディレクトリを開くときフラグを重ねてlpOverlappedパラメータへのポインタを提供これは、構造内のイベントを待って非同期I/Oを処理することを意味します。同時に、ReadDirectoryChangesW()をワーカースレッド内のループで呼び出します。私は最初にlpOverlappedに設定してください。は、専用のスレッドを持っており、同期モードを使用できるため、もう一度試してみます。

ReadDirectoryChangesW() API関数のドキュメントでは、それを使用するさまざまな方法が説明されています。バッファーがオーバーフローする可能性もあるため、変更イベントは失われる可能性があります。ディレクトリの内容のスナップショットを比較するだけで、この機能だけに頼る戦略を考え直すべきでしょう。

編集:

編集したコードが良く見えます。しかし私のテストではReadDirectoryChangesW()は広告された通りに動作しました。返されたバッファにいくつかのデータエントリがあったか、処理するバッファが複数ありました。これはタイミングに依存し、Delphiでブレークポイントを打った後、私は1つのバッファにいくつかのエントリを取得します。

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; 

本当にそれだけのための1つの変更、それに含まれるファイルのために何も返さないディレクトリを削除する:私は、テストコードを添付して完全にするため

は、デルファイ5使用して実装されています。親ディレクトリのハンドルだけを見ているので意味があります。サブディレクトリの通知が必要な場合は、おそらくそれらも監視する必要があります。

+0

遅れて申し訳ありませんが、以前は同期バージョンを使っていましたが、まったく同じ問題がありましたが、スレッドを完全に終了させる方法が見つからなかったため、非同期バージョンに切り替えました。私は、しかし、(WaitForMultipleObjectsへのブロッキング呼び出し、ファイル変更イベントまたは終了イベントのいずれかによって終了することができます)、サンプルコードで重要な行を1つ見逃していました。私はそれに応じて質問を編集する。 (...) – jpfollenius

+0

スナップショットはどういう意味ですか? FindFirstを使用してすべてのファイルを反復処理することを意味する場合、以前はこのようなアプローチを使用していましたが、大きなディレクトリを使用するときの変更検出の遅延が発生しないようにしたい、(2)インデックススレッドのI /その他のI/O操作 – jpfollenius

+1

2番目のコメントに同意しますが、MSDNのドキュメントの状態では、内部バッファのオーバーフローの準備が必要です。この場合、ディレクトリの完全な(再)スキャンが必要です。 – mghie

4

多くの変更が同時に発生した場合、特にイベントを失うことで同じ問題が発生しました。 500個のファイルが監視対象のディレクトリにコピーされます。

最後にCromisが見つかりました。Directory watchを使用しました。私たちは一度も振り返ったことがありません。

+0

ディレクトリウォッチは確かに良いです。 64ビット互換の場合は、getWindowLongをgetWindowLongPtrに置き換える必要があります(setの場合も同様です) – Ampere

関連する問題