Différences
Ci-dessous, les différences entre deux révisions de la page.
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 [07/05/2021 11:19] thierry [Comment ça marche ?] |
prog:lazarus:cas:files:watch [09/05/2021 15:18] (Version actuelle) thierry [Exemple de code] |
||
---|---|---|---|
Ligne 8: | Ligne 8: | ||
Pour faire simple : | Pour faire simple : | ||
- | On récupere les Handle des répértoires a surveiller avec | + | 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> | <code delphi> | ||
function FindFirstChangeNotification(lpPathName:LPCSTR; bWatchSubtree:WINBOOL; dwNotifyFilter:DWORD):HANDLE; | function FindFirstChangeNotification(lpPathName:LPCSTR; bWatchSubtree:WINBOOL; dwNotifyFilter:DWORD):HANDLE; | ||
Ligne 21: | Ligne 21: | ||
</code> | </code> | ||
- | Ensuite on attends une modification avec : | + | Ensuite on attends une modification avec [[https://docs.microsoft.com/fr-fr/windows/win32/api/synchapi/nf-synchapi-waitformultipleobjects|WaitForMultipleObjects]]: |
<code delphi> | <code delphi> | ||
function WaitForMultipleObjects( nCount:DWORD; lpHandles : PWOHandleArray; bWaitAll:WINBOOL; dwMilliseconds:DWORD):DWORD | function WaitForMultipleObjects( nCount:DWORD; lpHandles : PWOHandleArray; bWaitAll:WINBOOL; dwMilliseconds:DWORD):DWORD | ||
Ligne 31: | Ligne 31: | ||
La fonction '' WaitForMultipleObjects'' retourne un DWord qui peut être interprété de la façon suivante: | 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 | ||
- | WAIT_OBJECT_0 (0) + Index du Handle qui a été modifié | + | 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]] |
- | WAIT_ABANDONED_0 ($80) + + Index du Handle qui a été modifié | + | |
- | WAIT_TIMEOUT ($102) | + | |
- | WAIT_FAILED ($ffffffff) | + | |
- | Sachant que l'index max est $40 (MAXIMUM_WAIT_OBJECTS) | + | |
+ | 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 ???) | ||
- | ==== Titre ==== | + | 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 ? | ||