Me parece que usted está mezclando las diversas maneras de utilizar ReadDirectoryChangesW(), no tanto especificar el indicador ARCHIVO _ _ COMPROMETIDOS bandera al abrir el directorio y proporciona un puntero a la lpOverlapped parámetro , lo que significa que desea esperar el evento en la estructura y manejar la E/S asíncrona; y al mismo tiempo llama al ReadDirectoryChangesW() en un bucle en un hilo de trabajo. Primero probaría de nuevo con lpOverlapped establecido en nil, ya que tiene un hilo dedicado y puede usar el modo síncrono.
En la documentación de la función de API ReadDirectoryChangesW() se describen las diferentes formas de usarlo. Tenga en cuenta que también es posible que el búfer se desborde, por lo que los eventos de cambio se pueden perder de todos modos. Tal vez debería reconsiderar su estrategia de confiar únicamente en esta función, la comparación de instantáneas de los contenidos del directorio podría funcionar también.
Editar:
Su código editado ve mejor. En mis pruebas, sin embargo, ReadDirectoryChangesW() funcionaba como se anunciaba, había varias entradas de datos en el búfer devuelto, o había más de un búfer para procesar. Esto depende del tiempo, después de llegar a un punto de interrupción en Delphi, obtengo varias entradas en un búfer.
Para completar hay que adjuntar el código de prueba, implementado usando 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;
La eliminación de un directorio en efecto sólo se volverá un cambio para él, nada de los archivos contenidos en ella. Pero tiene sentido, ya que solo está viendo el manejo del directorio principal. Si necesita notificaciones para subdirectorios, probablemente necesite verlas también.
Lo siento por mi comentario demorado.Utilicé la versión sincrónica antes (con exactamente los mismos problemas) pero cambié a la versión asíncrona porque no encontré la manera de terminar el hilo de forma limpia. Sin embargo, perdí una línea importante en el código de ejemplo (la llamada de bloqueo a WaitForMultipleObjects, que puede ser termine por un evento de cambio de archivo o por un evento de terminación). Edito la pregunta en consecuencia. (...) – jpfollenius
¿Qué quiere decir con instantánea? Si quiere decir iterar sobre todos los archivos usando FindFirst, FindNext: Anteriormente utilicé dicho enfoque, pero quería evitar (1) tiempos de detección de cambios retrasados cuando utilizaba directorios grandes y (2) carga de E/S constante para el hilo de indexación que ralentizaba todo otras operaciones de E/S. – jpfollenius
Está de acuerdo con su segundo comentario, pero como la documentación de MSDN indica que debe estar preparado para los desbordamientos del búfer interno, y en ese caso, es necesario un escaneo (re) completo del directorio. – mghie