Il me semble que vous mélangez les différentes façons d'utiliser ReadDirectoryChangesW(), vous faites les deux spécifier le FICHIER _ FLAG _ CHEVAUCHENT drapeau lors de l'ouverture du répertoire et de fournir un pointeur sur le lpOverlapped paramètre , ce qui signifie que vous voulez attendre l'événement dans la structure et gérer les E/S asynchrones; et en même temps vous appelez ReadDirectoryChangesW() dans une boucle dans un thread de travail. Je voudrais d'abord essayer à nouveau avec lpOverlapped mis à nul, comme vous avez un thread dédié et pouvez utiliser le mode synchrone.
La documentation de la fonction API ReadDirectoryChangesW() décrit les différentes manières de l'utiliser. Notez qu'il est également possible que le tampon déborde, donc les événements de changement peuvent être perdus de toute façon. Peut-être que vous devriez repenser votre stratégie de ne compter que sur cette fonction, en comparant les instantanés du contenu du répertoire pourrait fonctionner aussi bien.
Edit:
Votre code modifié semble mieux. Dans mes tests cependant ReadDirectoryChangesW() a fonctionné comme annoncé, il y avait plusieurs entrées de données dans le tampon retourné, ou il y avait plus d'un tampon à traiter. Cela dépend du timing, après avoir frappé un point d'arrêt dans Delphi, j'ai plusieurs entrées dans un tampon.
Pour être complet, je joins le code de test, mis en œuvre en utilisant 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;
Suppression d'un répertoire ne fait revenir un seul changement pour elle, rien pour les fichiers qu'il contient. Mais cela a du sens, car vous ne regardez que la poignée du répertoire parent. Si vous avez besoin de notifications pour les sous-répertoires, vous devez probablement les regarder également.
Désolé pour mon commentaire différé.J'ai utilisé la version synchrone avant (avec exactement les mêmes problèmes) mais je suis passé à la version asynchrone parce que je n'ai pas trouvé un moyen de terminer proprement le thread. J'ai cependant manqué une ligne importante dans le code d'exemple (l'appel bloquant à WaitForMultipleObjects, qui peut soit être terminé par un événement de changement de fichier ou par un événement de fin). Je modifie la question en conséquence. (...) – jpfollenius
Qu'entendez-vous par instantané? Si vous voulez répéter sur tous les fichiers avec FindFirst, FindNext: J'ai précédemment utilisé une telle approche mais je voulais éviter (1) les temps de détection des changements retardés lors de l'utilisation de gros répertoires et (2) la charge constante des E/S pour le thread d'indexation autres opérations d'E/S. – jpfollenius
D'accord avec votre deuxième commentaire, mais comme la documentation de MSDN indique que vous devez être préparé pour les débordements de la mémoire tampon interne, et dans ce cas, une (re) analyse complète du répertoire est nécessaire. – mghie