Почему 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;
Фильтр
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! И когда я удаляю каталог, я получаю только одно событие для каталога. Что если я хочу одно событие для каждого файла в каталоге?
Любая помощь будет оценена.
2 ответа
Мне кажется, что вы смешиваете различные способы использования ReadDirectoryChangesW(), вы оба задаете флаг FILE_FLAG_OVERLAPPED при открытии каталога и предоставляете указатель на параметр lpOverlapped, что означает, что вы хотите дождаться события в структуре и обработать асинхронный ввод / вывод; и в то же время вы вызываете ReadDirectoryChangesW() в цикле в рабочем потоке. Сначала я бы попробовал снова, если для lpOverlapped установлено значение nil, поскольку у вас есть выделенный поток и вы можете использовать синхронный режим.
В документации API-функции ReadDirectoryChangesW() описаны различные способы ее использования. Обратите внимание, что также возможно переполнение буфера, поэтому события изменений могут быть потеряны в любом случае. Возможно, вам следует переосмыслить свою стратегию, полагаясь исключительно на эту функцию, так как сравнение снимков содержимого каталогов также может работать.
Редактировать:
Ваш отредактированный код выглядит лучше. Однако в моих тестах 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. Мы никогда не оглядывались назад.