Différences

Ci-dessous, les différences entre deux révisions de la page.

Lien vers cette vue comparative

Les deux révisions précédentes Révision précédente
Prochaine révision
Révision précédente
prog:lazarus:cas:files:watch [06/05/2021 19:03]
thierry [FindFirstChangeNotification]
prog:lazarus:cas:files:watch [09/05/2021 15:18] (Version actuelle)
thierry [Exemple de code]
Ligne 3: Ligne 3:
 ==== FindFirstChangeNotification ==== ==== FindFirstChangeNotification ====
 Cette méthode est documentée ici -> [[https://​docs.microsoft.com/​fr-fr/​windows/​win32/​fileio/​obtaining-directory-change-notifications|Obtention de notifications de modification de répertoire]] Cette méthode est documentée ici -> [[https://​docs.microsoft.com/​fr-fr/​windows/​win32/​fileio/​obtaining-directory-change-notifications|Obtention de notifications de modification de répertoire]]
 +
 +Apparemment cette méthode permet de savoir si un dossier a été modifier, mais pas de connaitre les modification apportées. Quel fichier modifié ? Quel fichier ajouté ou supprimé ?
 +=== Comment ça marche ? ===
 +Pour faire simple :
 +
 +On récupere les Handle des répértoires a surveiller avec [[https://​docs.microsoft.com/​en-us/​windows/​win32/​api/​fileapi/​nf-fileapi-findfirstchangenotificationa|FindFirstChangeNotification]]
 +<code delphi>
 +function FindFirstChangeNotification(lpPathName:​LPCSTR;​ bWatchSubtree:​WINBOOL;​ dwNotifyFilter:​DWORD):​HANDLE;​
 +</​code>​
 +  * ''​lpPathname''​ = PChar('​C:​\MonRepertoire\Etc\'​)
 +  * ''​bWatchSubtree''​ = boolean -> on surveille juste le répertoire (False) ou le répertoire et ses enfants ? (True)
 +  * ''​dwNotifyFilter''​ = dWord -> Changement que l'on veut capturer (Filename, Dirname, Attributes, Size...) [[https://​docs.microsoft.com/​en-us/​windows/​win32/​api/​fileapi/​nf-fileapi-findfirstchangenotificationa#​parameters|plus d'​infos ici]]
 +
 +On met ces Handle dans un array de ce type : (par exemple)
 +<code delphi>
 +THandleList = array [0..MAXIMUM_WAIT_OBJECTS - 1] of THandle;
 +</​code>​
 +
 +Ensuite on attends une modification avec [[https://​docs.microsoft.com/​fr-fr/​windows/​win32/​api/​synchapi/​nf-synchapi-waitformultipleobjects|WaitForMultipleObjects]]:​
 +<code delphi>
 +function WaitForMultipleObjects( nCount:​DWORD;​ lpHandles : PWOHandleArray;​ bWaitAll:​WINBOOL;​ dwMilliseconds:​DWORD):​DWORD
 +</​code>​
 +  * ''​nCount''​ = nombre de handle dans la liste
 +  * ''​lpHandles''​ = pointer vers le premier enregistrement de la liste de type ''​THandleList''​ (voir ci-dessus) ​ (dans notre cas=''​@HandleList[0]''​)
 +  * ''​bWaitAll''​ = Boolean -> On attend toutes les modifs ??? (''​False''​ dans notre cas)
 +  * ''​dwMilliseconds''​ = Temps d'​attente en Millisecondes
 +
 +La fonction ''​ WaitForMultipleObjects''​ retourne un DWord qui peut être interprété de la façon suivante:
 +  * $102 (258) = TIMEOUT, le temps d'​attente (''​dwMilliseconds''​) s'est écoulé.
 +  * $FFFFFFFF (-1) = FAILED !
 +  * $0..$40 = Index (+WAIT_OBJECT_0) du Handle dans la liste correspondant au répertoire qui a été modifié
 +  * $80..$C0 = Index (+WAIT_ABANDONED_0) du Handle qui... je sais pas trop
 +
 +Plus d'​infos -> [[https://​docs.microsoft.com/​fr-fr/​windows/​win32/​api/​synchapi/​nf-synchapi-waitformultipleobjects#​return-value|Valeur de retour de la fonction WaitForMultipleObjects]]
 +
 +On traite la valeur retournée par la fonction ''​ WaitForMultipleObjects''​.\\
 +Puis on retourne a la surveillance par un [[https://​docs.microsoft.com/​en-us/​windows/​win32/​api/​fileapi/​nf-fileapi-findnextchangenotification| FindNextChangeNotification]] et rebouclage sur  [[https://​docs.microsoft.com/​fr-fr/​windows/​win32/​api/​synchapi/​nf-synchapi-waitformultipleobjects|WaitForMultipleObjects]]:​
 +<code delphi>
 +function FindNextChangeNotification(hChangeHandle:​HANDLE):​WINBOOL;​
 +</​code>​
 +ou le parametre ''​hChangeHandle''​ est le Handle du répertoire modifié precedement.\\
 +Ce Handle est récuperable dans la liste des Handles THandleList passé a la fonction ''​WaitForMultipleObjects''​ a l'​index retourné par cette méme fonction... (suis-je bien claire ???) 
 +
 +Exemple de code (Dans un Thread, car ''​WaitForMultipleObjects''​ est une fonction bloquante...) :
 +<code delphi>
 +   // vIndex: integer;
 +   // vHandleList : THandleList (voir ci-dessus)
 +   // FCount = nombre de handle dans la liste vHandleList
 +   // FWaitMs = nombre de millisecondes a attendre
 +   // FDirChangedHandle = Handle du Find Change Notifier du répertoire qui a été modifié
 +   // Le Handle du Find Change Notifier est retourné par la function FindFirstChangeNotification (voir ci-dessus)
 +         while not Terminated do
 +           begin
 +            vIndex := WaitForMultipleObjects(FCount,​ vHandleList[0],​ False, FWaitMs);
 +            if (vIndex >= WAIT_OBJECT_0) and
 +               ​(vIndex < WAIT_OBJECT_0 + FNHandleList.Count) ​ then
 +              begin
 +                  FDirChangedHandle :​=FHandleList[vIndex - WAIT_OBJECT_0];​
 +                  ...
 +                  FindNextChangeNotification( FDirChangedHandle);​
 +              end;
 +           end;
 +</​code>​
 +==== ReadDirectoryChangesW ====
 +
 +=== Exemple de code ===
 +
 +Voici un exemple de code pour l'​utilisation de ''​ReadDirectoryChangesW'':​
 +
 +Documentation utile pour mieux comprendre ce code :
 +  * [[prog:​lazarus:​cas:​files:​createfile|Documentation sur la function CreateFile]]
 +  * [[https://​docs.microsoft.com/​en-us/​windows/​win32/​api/​winbase/​nf-winbase-readdirectorychangesw|Documentation sur la function ReadDirectoryChangesW]]
 +  * [[https://​docs.microsoft.com/​en-us/​windows/​win32/​api/​ioapiset/​nf-ioapiset-getoverlappedresult|Documentation sur la function GetOverlappedResult]]
 +  * [[https://​docs.microsoft.com/​en-us/​windows/​win32/​api/​winnt/​ns-winnt-file_notify_information|Documentation sur le type FILE_NOTIFY_INFORMATION]]
 +<code delphi DirMonDOS.lpr>​
 +program DirMonDOS;
 +
 +{$mode objfpc}{$H+}
 +
 +uses
 + ​{$IFDEF UNIX}
 + ​{$IFDEF UseCThreads}
 +   ​cthreads,​
 + ​{$ENDIF}
 + ​{$ENDIF}
 +   ​Classes,​
 +   crt,
 +   ​JwaWindows,​
 +   ​SysUtils;​
 +
 +var
 +   ​FPathName:​ string;
 +   ​FDirHandle:​ THandle;
 +   ​FBuffer: ​ array[0..9999] of byte;
 +   ​FBytesReturned:​ dword;
 +   ​FOverLapped:​ TOverlapped;​
 +   ​FOverResult:​ boolean;
 +   ​FFileNotifyInformation:​ PFileNotifyInformation;​
 +   ​FOffset: ​ dword;
 +   ​FAction: ​ dword;
 +   ​FActionStr:​ string;
 +   ​FFilename:​ string;
 +   ​FLen: ​    ​dword;​
 +   ​FTerminate:​ boolean;
 +   ​FLastError:​ dword;
 +   ​FNotifyFilter:​ DWord;
 +   ​FLooping:​ boolean;
 +
 +begin
 +   { Répertoire à surveiller }
 +   ​FPathName ​    := '​J:​\';​
 +
 +   { Initialisation des variables }
 +   ​FTerminate := False;
 +   ​FLooping ​  := False;
 +   ​ZeroMemory(@FBuffer,​ sizeof(FBuffer));​
 +   ​FNotifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME or FILE_NOTIFY_CHANGE_DIR_NAME or
 +      FILE_NOTIFY_CHANGE_LAST_WRITE;​
 +   { FILE_NOTIFY_CHANGE_FILE_NAME = 1;
 +     ​FILE_NOTIFY_CHANGE_DIR_NAME = 2;
 +     ​FILE_NOTIFY_CHANGE_ATTRIBUTES = 4;
 +     ​FILE_NOTIFY_CHANGE_SIZE = 8;
 +     ​FILE_NOTIFY_CHANGE_LAST_WRITE = 16;
 +     ​FILE_NOTIFY_CHANGE_LAST_ACCESS = 32;
 +     ​FILE_NOTIFY_CHANGE_CREATION = 64;
 +     ​FILE_NOTIFY_CHANGE_SECURITY = 256; }
 +
 +   ​writeln('​Press [s] to exit'​);​
 +   ​WriteLn('​Surveillance du dossier [', FPathName, '​]'​);​
 +
 +   { Récuperation du Handle du Répertoire a surveiller }
 +   ​FDirHandle := CreateFile(PChar(FPathName),​ FILE_LIST_DIRECTORY,​
 +      FILE_SHARE_READ or FILE_SHARE_DELETE or FILE_SHARE_WRITE,​ nil,
 +      OPEN_EXISTING,​ FILE_FLAG_BACKUP_SEMANTICS or FILE_FLAG_OVERLAPPED,​ 0);
 +   ​WriteLn('​Handle du dossier [', FDirHandle, '​]'​);​
 +
 +
 +   if FDirHandle <> INVALID_HANDLE_VALUE then
 +     begin
 +        try
 +          { Bouclage jusqu'​a ce que Terminate=True }
 +          repeat
 +             { Mise en place de la surveillance }
 +             if ReadDirectoryChangesW(FDirHandle,​ @FBuffer, sizeOf(FBuffer),​
 +               True, FNotifyFilter,​ @FBytesReturned,​ @FOverLapped,​ nil) then
 +              begin
 +
 +               { Bouclage jusqu'​a ce que FOverResult=True }
 +               ​repeat
 +                  { Si FOverResult = True, cela signifie qu'il y a eu des modifications }
 +                  FOverResult :=
 +                     ​GetOverlappedResult(FDirHandle,​ FOverLapped,​
 +                     ​FBytesReturned,​ False);
 +                  { Je capture GetLastError ci-dessous, car il peut etre modifié
 +                    par d'​autres instructions,​ comme un WriteLn par exemple }
 +                  FLastError ​ := GetLastError;​
 +
 +                  { Affichage des informations pour faire beau }
 +                  if not FOverResult and not FLooping then
 +                    begin
 +                     ​Writeln('​Looping...'​);​
 +                     ​FLooping := True;
 +                    end;
 +                  sleep(100);
 +                  { Verification si on a pressé la touche [S] pour arreter le processus }
 +                  if KeyPressed and (ReadKey = '​s'​) then
 +                     ​FTerminate := True;
 +
 +               until FOverResult or (FLastError <> ERROR_IO_INCOMPLETE) or FTerminate;
 +               ​FLooping := False;
 +
 +               if FOverResult then
 +                 begin
 +                  Writeln('​--- begin modification ---');
 +
 +                  { Positionnement sur le debut du Buffer }
 +                  pointer(FFileNotifyInformation) := @FBuffer[0];​
 +                  repeat
 +                     ​FOffset := FFileNotifyInformation^.NextEntryOffset;​
 +                     ​FAction := FFileNotifyInformation^.Action;​
 +                     { FLen = FileNameLength/​2
 +                       car FileName est codé en WideString
 +                       et WideString est 2x plus long que AnsiString
 +                       ​->​https://​wiki.freepascal.org/​Character_and_string_types/​fr }
 +                     ​FLen ​   := FFileNotifyInformation^.FileNameLength div 2;
 +                     ​WideCharLenToStrVar(@(FFileNotifyInformation^.FileName),​
 +                        FLen, FFilename);
 +
 +                     case FAction of
 +                        FILE_ACTION_ADDED:​ FActionStr ​   := '​Created';​
 +                        FILE_ACTION_REMOVED:​ FActionStr ​ := '​Deleted';​
 +                        FILE_ACTION_MODIFIED:​ FActionStr := '​Modified';​
 +                        FILE_ACTION_RENAMED_OLD_NAME:​ FActionStr := '​RenamedFrom';​
 +                        FILE_ACTION_RENAMED_NEW_NAME:​ FActionStr := '​RenamedTo';​
 +                        else
 +                           ​FActionStr := '???';​
 +                       end;
 +
 +                     { Affichage du résultat }
 +                     ​WriteLn('​Action [', FAction, '​|',​ FActionStr, '] len[',
 +                        FLen, '] sur fichier [', FFilename, '​]'​);​
 +
 +                     { Positionnement sur le prochain FFileNotifyInformation }
 +                     ​pointer(FFileNotifyInformation) :=
 +                        pointer(FFileNotifyInformation) + FOffset;
 +
 +                  until FOffset = 0;
 +                  { Nettoyage du Buffer pour eviter d'​accumuler des parasites
 +                    surtout dans Filename }
 +                  ZeroMemory(@FBuffer,​ sizeof(FBuffer));​
 +                  Writeln('​--- end modification ---');
 +                 end;
 +              end;
 +         until FTerminate;
 +        finally
 +         ​CloseHandle(FDirHandle);​
 +        end;
 +     end
 +   else
 +      Writeln('​Dir Handle invalid'​);​
 +   ​Writeln('​Press to exit'​);​
 +   ​Readln;​
 +
 +end.
 +</​code>​
 +=== Ressources pour ReadDirectoryChangesW ===
 +  * [[https://​www.osnews.com/​story/​7376/​a-directory-monitor-class-for-delphi/​]]
 +  * [[https://​www.experts-exchange.com/​questions/​24440582/​Using-ReadDirectoryChangesW-in-Delphi.html]]
 +  * [[https://​nono40.developpez.com/​sources/​source0045/​]]
 +
  
  
 ==== A la Procmon ==== ==== A la Procmon ====
-[[softs:​procmon|Procmon]] est un excellent utilitaire qui montre en temps réel les accès aux fichiers par les processus.\\+[[softs:​sysinternals:​procmon|Procmon]] est un excellent utilitaire qui montre en temps réel les accès aux fichiers par les processus.\\
 Reste a savoir comment il fait ? Reste a savoir comment il fait ?