Detect file change Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Detect file change

22 May 2020, 12:40

yeah makes sense i suppose

Code: Select all

DWORD __fastcall ThreadProc(LPVOID lpParameter) {
    DWORD WaitEvent = WaitForSingleObject(Handle, Timeout);
    return (DWORD)SendMessageW(hWnd, Msg, (WPARAM)Handle, WaitEvent);
}
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

22 May 2020, 14:11

I've added GetOverlappedResult.

Code: Select all

#NoEnv
SetBatchLines -1

folderPath1 := A_Desktop
folderPath2 := A_ScriptDir

FILE_NOTIFY_CHANGE_FILE_NAME  := 0x1
FILE_NOTIFY_CHANGE_DIR_NAME   := 0x2
FILE_NOTIFY_CHANGE_ATTRIBUTES := 0x4
FILE_NOTIFY_CHANGE_SIZE       := 0x8

notifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME
              | FILE_NOTIFY_CHANGE_DIR_NAME
              | FILE_NOTIFY_CHANGE_ATTRIBUTES
              | FILE_NOTIFY_CHANGE_SIZE
              
OnDirectoryChanges(folderPath1, "UserFunc", notifyFilter)
OnDirectoryChanges(folderPath2, "UserFunc", notifyFilter)

UserFunc(filePath, action) {
   MsgBox % A_ThisFunc . "`n" . ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][action] ": " filePath
}

OnDirectoryChanges(dirPath, procName := -1, notifyFilter := 3) {
   static DirList := {}, EventList, WM_ReadDirectoryChanges := 0xFFF

   IsObject(EventList) || EventList := DirectoryChangesEventListener(-1, 0, 0)

   if (procName = -1) {
      return EventList[DirList[dirPath]].UserDefinedProc.Name
   }
   else if (procName = "") {
      if DirList.HasKey(dirPath) && eventHandle := DirList.Delete(dirPath)
         return EventList.Delete(eventHandle).UserDefinedProc.Name
   }
   else if IsFunc(procName) {
      if DirList.HasKey(dirPath) && eventHandle := DirList.Delete(dirPath)
         priorProcName := EventList.Delete(eventHandle).UserDefinedProc.Name
      else priorProcName := procName

      if OnMessage(WM_ReadDirectoryChanges) != "DirectoryChangesEventListener"
         OnMessage(WM_ReadDirectoryChanges, "DirectoryChangesEventListener")

      Event := new _Event
      Event.Proc := new _AsyncReadDirectoryChanges(dirPath, notifyFilter)
      Event.Wait := new _AsyncWaitFunc(Event.handle, A_ScriptHWND, WM_ReadDirectoryChanges)
      Event.UserDefinedProc := Func(procName)
      Event.dirPath := dirPath

      DirList[dirPath] := Event.handle
      EventList[Event.handle] := Event

      Event.Proc()
      Event.Wait()

      return priorProcName
   }
   else return false
}

DirectoryChangesEventListener(wParam, lParam, msg) {
   static EventList := {}, WM_ReadDirectoryChanges := 0xFFF
   if (msg = WM_ReadDirectoryChanges && Event := EventList[wParam]) {
      if !DllCall("GetOverlappedResult", "Ptr", Event.Proc.handle, "Ptr", Event.Proc.GetAddress("buffer"), "UIntP", written, "UInt", true)
         Return
      action := Event.Proc.GetEventType()
      name := Event.Proc.GetObjectName()
      path := Event.dirPath . "\" . name
      Proc := Event.UserDefinedProc
      %Proc%(path, action)

      Event.Proc()
      Event.Wait()
   }
   else if (wParam = -1)
      return EventList
}

class _Event
{
   __New() {
      this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 1, "Int", 0, "Ptr")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      addr := this.GetAddress("overlapped")
      DllCall("RtlZeroMemory", "Ptr", addr, "Ptr", A_PtrSize*3 + 8)
      NumPut(this.handle, addr + A_PtrSize*2 + 8, "Ptr")
   }

   __Delete() {
      DllCall("CloseHandle", "Ptr", this.handle)
   }
}

class _AsyncWaitFunc
{
   __New(eventObjHandle, hWnd, msg, timeout := -1) {
      if !this.startAddress := CreateWaitFunc(eventObjHandle, hWnd, msg, timeout)
         throw Exception("Failed to create wait function.`n`nError code:`t" . A_LastError)
   }

   __Call(EventObj) {
      if IsObject(EventObj) {
         if this.threadHandle {
            DllCall("TerminateThread", "Ptr", this.threadHandle, "UInt", 0)
            DllCall("CloseHandle", "Ptr", this.threadHandle)
         }
         if !this.threadHandle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", this.startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
            throw Exception("Failed to create thread.`n`nError code:`t" . A_LastError)
         return this.threadHandle
      }
   }
   
   __Delete() {
      if this.threadHandle {
         DllCall("TerminateThread", "Ptr", this.threadHandle, "UInt", 0)
         DllCall("CloseHandle", "Ptr", this.threadHandle)
      }
   }
}

class _AsyncReadDirectoryChanges
{
   __New(dirPath, notifyFilter) {
     static FILE_SHARE_READ  := 1
          , FILE_SHARE_WRITE := 2
          , OPEN_EXISTING    := 3
          , FILE_FLAG_OVERLAPPED       := 0x40000000
          , FILE_FLAG_BACKUP_SEMANTICS := 0x02000000
      this.SetCapacity("buffer", 1024)
      this.notifyFilter := notifyFilter
      this.handle := DllCall("CreateFile", "Str", dirPath, "UInt", 1, "UInt", FILE_SHARE_READ|FILE_SHARE_WRITE, "Int", 0
                                                                    , "UInt", OPEN_EXISTING
                                                                    , "UInt", FILE_FLAG_OVERLAPPED|FILE_FLAG_BACKUP_SEMANTICS, "Int", 0, "Ptr")
   }

   GetEventType() {
      return NumGet(this.GetAddress("buffer") + 4,  "UInt")
   }

   GetObjectName() {
      bufferRef := this.GetAddress("buffer")
      return StrGet(bufferRef + 12, NumGet(bufferRef + 8, "UInt")//2)
   }

   __Call(EventObj) {
      if IsObject(EventObj) {
         return DllCall("ReadDirectoryChanges", "Ptr", this.handle, "Ptr", this.GetAddress("buffer"), "UInt", 1024, "Int", 1
                                              , "UInt", this.notifyFilter, "UInt", 0, "Ptr", EventObj.GetAddress("overlapped"), "Int", 0)
      }
   }

   __Delete() {
      DllCall("CloseHandle", "Ptr", this.handle)
   }
}

CreateWaitFunc(Handle, hWnd, Msg, Timeout=-1)
{
   static MEM_COMMIT := 0x1000, PAGE_EXECUTE_READWRITE := 0x40
   ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", A_PtrSize = 4 ? 49 : 85, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
   hModule := DllCall("GetModuleHandle", "Str", "kernel32.dll", "Ptr")
   pWaitForSingleObject := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "WaitForSingleObject", "Ptr")
   hModule := DllCall("GetModuleHandle", "Str", "user32.dll", "Ptr")
   pSendMessageW := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "SendMessageW", "Ptr")
   NumPut(pWaitForSingleObject, ptr*1)
   NumPut(pSendMessageW, ptr + A_PtrSize)
   if (A_PtrSize = 4)  {
      NumPut(0x68, ptr + 8, "UChar")
      NumPut(Timeout, ptr + 9, "UInt"), NumPut(0x68, ptr + 13, "UChar")
      NumPut(Handle, ptr + 14), NumPut(0x15FF, ptr + 18, "UShort")
      NumPut(ptr, ptr + 20), NumPut(0x6850, ptr + 24, "UShort")
      NumPut(Handle, ptr + 26), NumPut(0x68, ptr + 30, "UChar")
      NumPut(Msg, ptr + 31, "UInt"), NumPut(0x68, ptr + 35, "UChar")
      NumPut(hWnd, ptr + 36), NumPut(0x15FF, ptr + 40, "UShort")
      NumPut(ptr+4, ptr + 42), NumPut(0xC2, ptr + 46, "UChar"), NumPut(4, ptr + 47, "UShort")
   }
   else  {
      NumPut(0x53, ptr + 16, "UChar")
      NumPut(0x20EC8348, ptr + 17, "UInt"), NumPut(0xBACB8948, ptr + 21, "UInt")
      NumPut(Timeout, ptr + 25, "UInt"), NumPut(0xB948, ptr + 29, "UShort")
      NumPut(Handle, ptr + 31), NumPut(0x15FF, ptr + 39, "UShort")
      NumPut(-45, ptr + 41, "UInt"), NumPut(0xB849, ptr + 45, "UShort")
      NumPut(Handle, ptr + 47), NumPut(0xBA, ptr + 55, "UChar")
      NumPut(Msg, ptr + 56, "UInt"), NumPut(0xB948, ptr + 60, "UShort")
      NumPut(hWnd, ptr + 62), NumPut(0xC18941, ptr + 70, "UInt")
      NumPut(0x15FF, ptr + 73, "UShort"), NumPut(-71, ptr + 75, "UInt")
      NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B, ptr + 83, "UShort")
   }
   Return ptr + A_PtrSize*2
}
What else could be done to improve safety? Can TerminateThread be replaced with somewhat without CreateWaitFunc rewriting?
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

23 May 2020, 02:19

The best way to terminate a thread is to let it finish. You can signal the event (SetEvent) so the wait (WaitForSingleObject) end.

Cheers.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

23 May 2020, 20:40

Thanks! Damn, I had to guess myself! :headwall: I will post the code later.
malcev
Posts: 1769
Joined: 12 Aug 2014, 12:37

Re: Detect file change

23 May 2020, 21:25

swagfag wrote:
21 May 2020, 23:56
what does this do? post the source
The code made by Александр_.

Code: Select all

; Функция потока
; Ожидает возврата из WaitForSingleObject и отправляет сообщение окну
; lParam содержит возвращаемое WaitForSingleObject значение
VarSetCapacity(springboard, 38)
NumPut(0x68, springboard, 0, "Char")
NumPut(INFINITE, springboard, 1)
;push -1
NumPut(0x68, springboard, 5, "Char")
;NumPut(hChangeHandle, springboard, 6)
;push hChangeHandle
NumPut(0x15ff, springboard, 10, "Short")
NumPut(&WaitForSingleObjectAddr, springboard, 12)
;call WaitForsingleObject
NumPut(0x50, springboard, 16, "Char")
;push EAX
NumPut(0x6A, springboard, 17, "Char")
NumPut(0x00, springboard, 18, "Char")
;push 0
NumPut(0x68, springboard, 19, "Char")
NumPut(WM_USER, springboard, 20)
;push 0x400
NumPut(0x68, springboard, 24, "Char")
NumPut(hWnd, springboard, 25)
;push hWnd
NumPut(0x15ff, springboard, 29, "Short")
NumPut(&PostMessageAddr, springboard, 31)
;Call PostMessage
NumPut(0xC2, springboard, 35,"char")
NumPut(4, springboard, 36,"short")
;ret 4
http://forum.script-coding.com/viewtopic.php?id=6273

Also there is another code by YMP:

Code: Select all

DirName := A_ScriptDir          ; Папка для слежения.
LogFile := A_Desktop . "\DirLog.txt"

F10:: WatchDirectory(DirName)   ; Начать слежение.
F11:: WatchDirectory(0)         ; Остановить.


; ================ Функции =====================================================

WM_DIRECTORYCHANGE(BytesReturned, pOutBuf)
{
    Global LogFile
    ;FILE_ACTION_ADDED := 1, FILE_ACTION_REMOVED := 2, FILE_ACTION_MODIFIED := 3
    ;FILE_ACTION_RENAMED_OLD_NAME := 4, FILE_ACTION_RENAMED_NEW_NAME := 5
    Static Actions := ["Файл добавлен:  ", "Файл удалён:    ", "Файл изменён:   "
                     , "Файл переименован с имени: ", "Файл переименован на имя:  "]
    If (pOutBuf = 0) {
        MsgBox, Ошибка в ReadDirectoryChangeW
        Return
    }
    DateTime := A_DD "." A_MM "." A_YYYY " " A_Hour ":" A_Min ":" A_Sec
    Addr := pOutBuf, Next := 0  ; Адрес текущей записи и смещение до следующей.
    Loop    ; Чтение из буфера записей о событиях и реакция на них.
    {
        Addr += Next, Next := NumGet(Addr+0, 0, "uint") ; Смещение следующей записи.
        ActionCode := NumGet(Addr+0, 4, "uint") ; Код события (см. в начале функции).
        If (Action := Actions[ActionCode]) {    ; Описание события из массива.
            cbFile := NumGet(Addr+0, 8, "uint") ; Длина имени файла в байтах.
            FileName := StrGet(Addr+12, cbFile // 2, "utf-16")
            Msg .= DateTime " " Action FileName "`n"
        }
        If (!Next)  ; Если смещение равно 0, больше записей в буфере нет.
            Break
    }
    If (Msg) {
        ToolTip, %Msg%
        If (LogFile)
            FileAppend, %Msg%, %LogFile%
        Sleep, 1000
        ToolTip
    }
    WatchDirectory(-1)  ; Продолжить слежение.
}

WatchDirectory(DirName)
{
    Static hDir, hThread, pData, pThreadStart, OutBuf, OutBufSize := 0x400  ; 1 KB
    Static BytesReturned, WM_DIRECTORYCHANGE := 0x401
    ;FILE_NOTIFY_CHANGE_FILE_NAME := 0x1, FILE_NOTIFY_CHANGE_DIR_NAME := 0x2
    ;FILE_NOTIFY_CHANGE_ATTRIBUTES := 0x4, FILE_NOTIFY_CHANGE_SIZE := 0x8
    ;FILE_NOTIFY_CHANGE_LAST_WRITE := 0x10, FILE_NOTIFY_CHANGE_LAST_ACCESS := 0x20
    ;FILE_NOTIFY_CHANGE_CREATION := 0x40, FILE_NOTIFY_CHANGE_SECURITY := 0x100
    Static NotifyFilter := 0x11     ; Комбинация из флагов выше (сумма).
    If (DirName = -1) {
        DllCall("CloseHandle", "ptr", hThread)
        Goto NewThread
    }
    Else If (DirName = 0) {
        If (hThread) {
            DllCall("TerminateThread", "ptr", hThread, "int", 0)
            DllCall("CloseHandle", "ptr", hThread), hThread := 0
        }
        If (hDir)
            DllCall("CloseHandle", "ptr", hDir), hDir := 0
        Return
    }
    If (hDir)
        WatchDirectory(0) ; Остановить текущее слежение.
    If (!OutBuf) {
        VarSetCapacity(OutBuf, OutBufSize, 0), VarSetCapacity(BytesReturned, 4, 0)
        OnMessage(WM_DIRECTORYCHANGE, "WM_DIRECTORYCHANGE")
        If !(pReadDirectoryChanges := GetProcAddress("kernel32.dll", "ReadDirectoryChangesW"))
            Return Error("GetProcAddress - ReadDirectoryChangesW")
        If !(pPostMessage := GetProcAddress("user32.dll", "PostMessage" . (A_IsUnicode? "W":"A")))
            Return Error("GetProcAddress - PostMessage")
        If !(pThreadStart := CreateMachineFunc())
            Return Error("CreateMachineFunc")
        pData := CreateStruct(pReadDirectoryChanges, hDir, &OutBuf, OutBufSize, 0
                            , NotifyFilter, &BytesReturned, 0, 0
                            , pPostMessage, A_ScriptHwnd, WM_DIRECTORYCHANGE)
    }
    If !(hDir := OpenDirectory(DirName))
        Return Error("OpenDirectory")
    NumPut(hDir, pData+0, A_PtrSize, "ptr")
NewThread:
    If !(hThread := CreateThread(pThreadStart, pData))
        Return Error("CreateThread")
    Return True
}

OpenDirectory(Dir)
{
    Static FILE_LIST_DIRECTORY := 1, FILE_SHARE_READ := 1, FILE_SHARE_WRITE := 2
    Static OPEN_EXISTING := 3, FILE_FLAG_BACKUP_SEMANTICS := 0x02000000
    Static INVALID_HANDLE_VALUE := -1
    hDir := DllCall("CreateFile", "str", Dir, "uint", FILE_LIST_DIRECTORY
            , "uint", FILE_SHARE_READ | FILE_SHARE_WRITE, "ptr", 0, "uint", OPEN_EXISTING
            , "uint", FILE_FLAG_BACKUP_SEMANTICS, "ptr", 0, "ptr")
    Return hDir = INVALID_HANDLE_VALUE? 0:hDir      
}

CreateStruct(Members*)
{
    Static Struct
    cMembers := Members.MaxIndex()
    VarSetCapacity(Struct, cMembers * A_PtrSize, 0)
    addr := &Struct
    Loop, %cMembers%
        addr := NumPut(Members[A_Index], addr+0, 0, "ptr")
    Return &Struct
}

GetProcAddress(Lib, Func)
{
    hLib := DllCall("LoadLibrary", "str", Lib, "ptr")
    If (hLib = 0)
        Return 0
    Return DllCall("GetProcAddress", "ptr", hLib, "astr", Func, "ptr")
}

CreateMachineFunc()
{
    MEM_RESERVE := 0x2000, MEM_COMMIT := 0x1000, PAGE_EXECUTE_READWRITE := 0x40
    If (A_PtrSize = 8) {
        Hex = 
        ( Join LTrim
        488B0151FF7140FF7138FF7130FF71284883EC204C8B49204C8B4118488B5110488B4908FFD04
        C8B5424404D31C985C04D0F454A10498B4230448B00498B5258498B4A5041FF52484883C448C3
        )
    }
    Else {
        Hex =
        ( Join LTrim
        8B54240452FF7220FF721CFF7218FF7214FF7210FF720CFF7208FF7204FF125A85C00F4542088
        B4A1850FF31FF722CFF7228FF5224C20400
        )
    }
    Len := StrLen(Hex) // 2
    pFunc := DllCall("VirtualAlloc", "ptr", 0, "ptr", Len
                                   , "uint", MEM_RESERVE | MEM_COMMIT
                                   , "uint", PAGE_EXECUTE_READWRITE, "ptr")
    If (pFunc = 0)
        Return 0
    Loop, % Len
        NumPut("0x" . SubStr(Hex, A_Index * 2 - 1, 2), pFunc + 0
                                 , A_Index - 1, "uchar")
    Return pFunc
}

CreateThread(StartAddr, Param)
{
    Return DllCall("CreateThread", "ptr", 0, "ptr", 0, "ptr", StartAddr
                                 , "ptr", Param, "uint", 0, "ptr", 0, "ptr")
}

Error(Func)
{
    MsgBox, %Func% failed.
    Return False
}
Machine code GoAsm:

Code: Select all

#if x64

    mov rax,[rcx]
    push rcx,[rcx+40h],[rcx+38h],[rcx+30h],[rcx+28h]
    sub rsp,20h
    mov r9,[rcx+20h]
    mov r8,[rcx+18h]
    mov rdx,[rcx+10h]
    mov rcx,[rcx+8h]
    call rax            ; Вызов ReadDirectoryChangeW
    mov r10,[rsp+40h]
    xor r9,r9
    test eax,eax
    cmovnz r9,[r10+10h]
    mov rax,[r10+30h]
    mov r8d,[rax]
    mov rdx,[r10+58h]
    mov rcx,[r10+50h]
    call [r10+48h]      ; Вызов PostMessage
    add rsp,48h
    ret

#else

    mov edx,[esp+4]
    push edx
    push [edx+20h],[edx+1Ch],[edx+18h],[edx+14h],[edx+10h],[edx+0Ch],[edx+8h],[edx+4h]
    call [edx]
    pop edx
    test eax,eax
    cmovnz eax,[edx+8h]
    mov ecx,[edx+18h]
    push eax,[ecx],[edx+2Ch],[edx+28h]
    call [edx+24h]
    ret 4

#endif
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

23 May 2020, 23:25

The code by YMP is not safe, but can probably be made safe by calling CancelIoEx appropriately. It can only watch one directory at a time.

Edit, I think the simplest way to use ReadDirectoryChangeW is to specify a completion routine. I will write a function for v2 to demonstrate when I get time.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

26 May 2020, 13:45

This code seems to work well:

Code: Select all

#NoEnv
SetBatchLines -1

FILE_NOTIFY_CHANGE_FILE_NAME  := 0x1
FILE_NOTIFY_CHANGE_DIR_NAME   := 0x2
FILE_NOTIFY_CHANGE_ATTRIBUTES := 0x4
FILE_NOTIFY_CHANGE_SIZE       := 0x8

notifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME
              | FILE_NOTIFY_CHANGE_DIR_NAME
              | FILE_NOTIFY_CHANGE_ATTRIBUTES
              | FILE_NOTIFY_CHANGE_SIZE

folderPath1 := A_Desktop
folderPath2 := A_ScriptDir
              
Inst1 := new FileMonitoring(folderPath1, notifyFilter, "OnDirectoryChanged1")
Inst2 := new FileMonitoring(folderPath2, notifyFilter, "OnDirectoryChanged2")
Return

OnDirectoryChanged1(filePath, event) {
   MsgBox, % ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][event] ": " filePath
}

OnDirectoryChanged2(filePath, event) {
   MsgBox, % ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][event] ": " filePath
}

class FileMonitoring
{
   __New(folderPath, notifyFilter, UserFunc) {
      this.Event := new this._Event()
      this.SetCapacity("buffer", 1024)
      pBuffer := this.GetAddress("buffer")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      this.pOverlapped := this.GetAddress("overlapped")
      this.Directory := new this._ReadDirectoryChanges(folderPath, notifyFilter, pBuffer, this.pOverlapped, this.Event.handle)
      this.EventSignal := new this._EventSignal(this.Directory, this.Event.handle, pBuffer, UserFunc)
      this.Directory.Read()
   }
   
   __Delete() {
      DllCall("CancelIoEx", "Ptr", this.Directory.handle, "Ptr", this.pOverlapped)
      this.Event := ""
      this.EventSignal.Clear()
      this.Directory.Clear()
      this.SetCapacity("buffer", 0)
      this.buffer := ""
   }
   
   class _Event {
      __New() {
         this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 0, "Int", 0, "Ptr")
      }
      
      __Delete() {
         DllCall("SetEvent", "Ptr", this.handle)
         Sleep, 20
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _ReadDirectoryChanges {
      __New(dirPath, notifyFilter, pBuffer, pOverlapped, hEvent) {
         static access := (FILE_SHARE_READ := 1) | (FILE_SHARE_WRITE := 2), OPEN_EXISTING := 3
              , flags := (FILE_FLAG_OVERLAPPED := 0x40000000) | (FILE_FLAG_BACKUP_SEMANTICS := 0x2000000)
         for k, v in ["notifyFilter", "pBuffer", "pOverlapped", "hEvent"]
            this[v] := %v%
         this.handle := DllCall("CreateFile", "Str", dirPath, "UInt", 1, "UInt", access, "Int", 0
                                            , "UInt", OPEN_EXISTING, "UInt", flags, "Int", 0, "Ptr")
      }
      
      Read() {
         DllCall("msvcrt\memset", "Ptr", this.pOverlapped, "Int", 0, "Ptr", A_PtrSize*3 + 8, "Cdecl")
         NumPut(this.hEvent, this.pOverlapped + A_PtrSize*2 + 8, "Ptr")
         ; there is only a Unicode version of this api
         Return DllCall("ReadDirectoryChangesW", "Ptr", this.handle, "Ptr", this.pBuffer, "UInt", 1024, "Int", 1
                                               , "UInt", this.notifyFilter, "UInt", 0, "Ptr", this.pOverlapped, "Ptr", 0)
      }
      
      Clear() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _EventSignal {
      __New(Directory, hEvent, pBuffer, UserFunc) {
         this.WM_EVENTSIGNAL := DllCall("RegisterWindowMessage", "Str", "WM_EVENTSIGNAL", "UInt")
         for k, v in ["Directory", "hEvent", "pBuffer"]
            this[v] := %v%
         this.UserFunc := IsObject(UserFunc) ? UserFunc : Func(UserFunc)
         this.OnEvent := ObjBindMethod(this, "On_WM_EVENTSIGNAL")
         OnMessage(this.WM_EVENTSIGNAL, this.OnEvent)
         this.startAddress := this.CreateWaitFunc(this.hEvent, A_ScriptHwnd, this.WM_EVENTSIGNAL)
         this.Thread := new this._Thread(this.startAddress)
      }
      
      On_WM_EVENTSIGNAL(wp) {
         if !(wp = this.hEvent && DllCall("GetOverlappedResult", "Ptr", this.hEvent, "Ptr", this.pBuffer, "UIntP", written, "UInt", false))
            Return
         
         addr := this.pBuffer
         offset := 0
         Loop {
            addr += offset
            eventType  := NumGet(addr + 4, "UInt")
            objectName := StrGet(addr + 12, NumGet(addr + 8, "UInt")//2, "UTF-16") ; always in Unicode
            timer := this.UserFunc.Bind(objectName, eventType)
            SetTimer, % timer, -10
         } until !offset := NumGet(addr + 0, "UInt")
         this.Thread := new this._Thread(this.startAddress)
         this.Directory.Read()
      }
      
      class _Thread {
         __New(startAddress) {
            if !this.handle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
               throw Exception("Failed to create thread.`nError code: " . A_LastError)
         }
         __Delete() {
            DllCall("CloseHandle", "Ptr", this.handle)
         }
      }
      
      Clear() {
         this.Thread := ""
         OnMessage(this.WM_EVENTSIGNAL, this.OnEvent, 0)
         this.OnEvent := ""
         DllCall("VirtualFree", "Ptr", this.startAddress - A_PtrSize*2, "Ptr", A_PtrSize = 4 ? 49 : 85, "UInt", MEM_DECOMMIT := 0x4000)
      }

      CreateWaitFunc(Handle, hWnd, Msg, Timeout=-1)
      {
         static MEM_COMMIT := 0x1000, PAGE_EXECUTE_READWRITE := 0x40
         ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", A_PtrSize = 4 ? 49 : 85, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
         hModule := DllCall("GetModuleHandle", "Str", "kernel32.dll", "Ptr")
         pWaitForSingleObject := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "WaitForSingleObject", "Ptr")
         hModule := DllCall("GetModuleHandle", "Str", "user32.dll", "Ptr")
         pSendMessageW := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "SendMessageW", "Ptr")
         NumPut(pWaitForSingleObject, ptr*1)
         NumPut(pSendMessageW, ptr + A_PtrSize)
         if (A_PtrSize = 4)  {
            NumPut(0x68, ptr + 8, "UChar")
            NumPut(Timeout, ptr + 9, "UInt"), NumPut(0x68, ptr + 13, "UChar")
            NumPut(Handle, ptr + 14), NumPut(0x15FF, ptr + 18, "UShort")
            NumPut(ptr, ptr + 20), NumPut(0x6850, ptr + 24, "UShort")
            NumPut(Handle, ptr + 26), NumPut(0x68, ptr + 30, "UChar")
            NumPut(Msg, ptr + 31, "UInt"), NumPut(0x68, ptr + 35, "UChar")
            NumPut(hWnd, ptr + 36), NumPut(0x15FF, ptr + 40, "UShort")
            NumPut(ptr+4, ptr + 42), NumPut(0xC2, ptr + 46, "UChar"), NumPut(4, ptr + 47, "UShort")
         }
         else  {
            NumPut(0x53, ptr + 16, "UChar")
            NumPut(0x20EC8348, ptr + 17, "UInt"), NumPut(0xBACB8948, ptr + 21, "UInt")
            NumPut(Timeout, ptr + 25, "UInt"), NumPut(0xB948, ptr + 29, "UShort")
            NumPut(Handle, ptr + 31), NumPut(0x15FF, ptr + 39, "UShort")
            NumPut(-45, ptr + 41, "UInt"), NumPut(0xB849, ptr + 45, "UShort")
            NumPut(Handle, ptr + 47), NumPut(0xBA, ptr + 55, "UChar")
            NumPut(Msg, ptr + 56, "UInt"), NumPut(0xB948, ptr + 60, "UShort")
            NumPut(hWnd, ptr + 62), NumPut(0xC18941, ptr + 70, "UInt")
            NumPut(0x15FF, ptr + 73, "UShort"), NumPut(-71, ptr + 75, "UInt")
            NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B, ptr + 83, "UShort")
         }
         Return ptr + A_PtrSize*2
      }
   }
}
Last edited by teadrinker on 28 May 2020, 10:45, edited 1 time in total.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

28 May 2020, 10:07

teadrinker wrote:
26 May 2020, 13:45
This code seems to work well:
I only browsed it, it seems fine. You need

Code: Select all

this.onevent := ""
in _EventSignal.Clear() to clear circular reference.
I think the simplest way to use ReadDirectoryChangeW is to specify a completion routine. I will write a function for v2 to demonstrate when I get time.
See :arrow: [a109] watch_folder.

Cheers.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

28 May 2020, 10:37

Thanks!
Aha, you used the timer instead of a wait function. :)
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

28 May 2020, 11:56

Yes, it is basically just doing,

Code: Select all

; Not real code
h := CreateFile()
ReadDirectoryChangesW(h, ..., registerCallback("MyCallback"))
settimer "SleepEx"
Just a note about the sleep 20 in _Event.__delete(), there is no guarantee that the waiting thread sends the message during this time. It most likely will on a computer with multiple available cpu cores, but might not on a single cpu core system or on a system under heavy load. And consequently, there is no guarantee that the thread has finished when you free the wait func. I think you should wait for the thread to finish, eg, WaitForSingleObject, but you need to change SendMessage to PostMessage, otherwise you might dead lock.

Cheers.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

28 May 2020, 12:56

Yeah, I thought about a SendMessage's delay, but unfortunately I have a not enough knowledge to rewrite the CreateWaitFunc. Can you help, maybe? It's a great thing, and could be used for many different purposes.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

29 May 2020, 00:32

teadrinker wrote: Yeah, I thought about a SendMessage's delay, but unfortunately I have a not enough knowledge to rewrite the CreateWaitFunc. Can you help, maybe? It's a great thing, and could be used for many different purposes.
You can, without any fear, change this line

Code: Select all

pSendMessageW := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "SendMessageW", "Ptr")
to

Code: Select all

pSendMessageW := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "PostMessageW", "Ptr")
you might also want to change the name of the variable for readability ;).
Cheers :tea:.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

29 May 2020, 07:14

Got it! I've added WaitForSingleObject to _Thread's __Delete(). Does this look good?

Code: Select all

#NoEnv
SetBatchLines -1

FILE_NOTIFY_CHANGE_FILE_NAME  := 0x1
FILE_NOTIFY_CHANGE_DIR_NAME   := 0x2
FILE_NOTIFY_CHANGE_ATTRIBUTES := 0x4
FILE_NOTIFY_CHANGE_SIZE       := 0x8

notifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME
              | FILE_NOTIFY_CHANGE_DIR_NAME
              | FILE_NOTIFY_CHANGE_ATTRIBUTES
              | FILE_NOTIFY_CHANGE_SIZE

folderPath1 := A_Desktop
folderPath2 := A_ScriptDir
              
Inst1 := new FileMonitoring(folderPath1, notifyFilter, "OnDirectoryChanged1")
Inst2 := new FileMonitoring(folderPath2, notifyFilter, "OnDirectoryChanged2")
Return

OnDirectoryChanged1(filePath, event) {
   MsgBox, % ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][event] ": " filePath
}

OnDirectoryChanged2(filePath, event) {
   MsgBox, % ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][event] ": " filePath
}

class FileMonitoring
{
   __New(folderPath, notifyFilter, UserFunc) {
      this.Event := new this._Event()
      this.SetCapacity("buffer", 1024)
      pBuffer := this.GetAddress("buffer")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      this.pOverlapped := this.GetAddress("overlapped")
      this.Directory := new this._ReadDirectoryChanges(folderPath, notifyFilter, pBuffer, this.pOverlapped, this.Event.handle)
      this.EventSignal := new this._EventSignal(this.Directory, this.Event.handle, pBuffer, UserFunc)
      this.Directory.Read()
   }
   
   __Delete() {
      DllCall("CancelIoEx", "Ptr", this.Directory.handle, "Ptr", this.pOverlapped)
      this.Event := ""
      this.EventSignal.Clear()
      this.Directory.Clear()
      this.SetCapacity("buffer", 0)
      this.buffer := ""
   }
   
   class _Event {
      __New() {
         this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 0, "Int", 0, "Ptr")
      }
      
      __Delete() {
         DllCall("SetEvent", "Ptr", this.handle)
         Sleep, 20
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _ReadDirectoryChanges {
      __New(dirPath, notifyFilter, pBuffer, pOverlapped, hEvent) {
         static access := (FILE_SHARE_READ := 1) | (FILE_SHARE_WRITE := 2), OPEN_EXISTING := 3
              , flags := (FILE_FLAG_OVERLAPPED := 0x40000000) | (FILE_FLAG_BACKUP_SEMANTICS := 0x2000000)
         for k, v in ["notifyFilter", "pBuffer", "pOverlapped", "hEvent"]
            this[v] := %v%
         this.handle := DllCall("CreateFile", "Str", dirPath, "UInt", 1, "UInt", access, "Int", 0
                                            , "UInt", OPEN_EXISTING, "UInt", flags, "Int", 0, "Ptr")
      }
      
      Read() {
         DllCall("msvcrt\memset", "Ptr", this.pOverlapped, "Int", 0, "Ptr", A_PtrSize*3 + 8, "Cdecl")
         NumPut(this.hEvent, this.pOverlapped + A_PtrSize*2 + 8, "Ptr")
         ; there is only a Unicode version of this api
         Return DllCall("ReadDirectoryChangesW", "Ptr", this.handle, "Ptr", this.pBuffer, "UInt", 1024, "Int", 1
                                               , "UInt", this.notifyFilter, "UInt", 0, "Ptr", this.pOverlapped, "Ptr", 0)
      }
      
      Clear() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _EventSignal {
      __New(Directory, hEvent, pBuffer, UserFunc) {
         this.WM_EVENTSIGNAL := DllCall("RegisterWindowMessage", "Str", "WM_EVENTSIGNAL", "UInt")
         for k, v in ["Directory", "hEvent", "pBuffer"]
            this[v] := %v%
         this.UserFunc := IsObject(UserFunc) ? UserFunc : Func(UserFunc)
         this.OnEvent := ObjBindMethod(this, "On_WM_EVENTSIGNAL")
         OnMessage(this.WM_EVENTSIGNAL, this.OnEvent)
         this.startAddress := this.CreateWaitFunc(this.hEvent, A_ScriptHwnd, this.WM_EVENTSIGNAL)
         this.Thread := new this._Thread(this.startAddress)
      }
      
      On_WM_EVENTSIGNAL(wp) {
         if !(wp = this.hEvent && DllCall("GetOverlappedResult", "Ptr", this.hEvent, "Ptr", this.pBuffer, "UIntP", written, "UInt", false))
            Return
         
         addr := this.pBuffer
         offset := 0
         Loop {
            addr += offset
            eventType  := NumGet(addr + 4, "UInt")
            objectName := StrGet(addr + 12, NumGet(addr + 8, "UInt")//2, "UTF-16") ; always in Unicode
            timer := this.UserFunc.Bind(objectName, eventType)
            SetTimer, % timer, -10
         } until !offset := NumGet(addr + 0, "UInt")
         this.Thread := new this._Thread(this.startAddress)
         this.Directory.Read()
      }
      
      class _Thread {
         __New(startAddress) {
            if !this.handle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
               throw Exception("Failed to create thread.`nError code: " . A_LastError)
         }
         __Delete() {
            DllCall("WaitForSingleObject", "Ptr", this.handle, "Int", -1)
            DllCall("CloseHandle", "Ptr", this.handle)
         }
      }
      
      Clear() {
         this.Thread := ""
         OnMessage(this.WM_EVENTSIGNAL, this.OnEvent, 0)
         this.OnEvent := ""
         DllCall("VirtualFree", "Ptr", this.startAddress - A_PtrSize*2, "Ptr", A_PtrSize = 4 ? 49 : 85, "UInt", MEM_DECOMMIT := 0x4000)
      }

      CreateWaitFunc(Handle, hWnd, Msg, Timeout=-1)
      {
         static MEM_COMMIT := 0x1000, PAGE_EXECUTE_READWRITE := 0x40
         ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", A_PtrSize = 4 ? 49 : 85, "UInt", MEM_COMMIT, "UInt", PAGE_EXECUTE_READWRITE, "Ptr")
         hModule := DllCall("GetModuleHandle", "Str", "kernel32.dll", "Ptr")
         pWaitForSingleObject := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "WaitForSingleObject", "Ptr")
         hModule := DllCall("GetModuleHandle", "Str", "user32.dll", "Ptr")
         pPostMessageW := DllCall("GetProcAddress", "Ptr", hModule, "AStr", "PostMessageW", "Ptr")
         NumPut(pWaitForSingleObject, ptr*1)
         NumPut(pPostMessageW, ptr + A_PtrSize)
         if (A_PtrSize = 4)  {
            NumPut(0x68, ptr + 8, "UChar")
            NumPut(Timeout, ptr + 9, "UInt"), NumPut(0x68, ptr + 13, "UChar")
            NumPut(Handle, ptr + 14), NumPut(0x15FF, ptr + 18, "UShort")
            NumPut(ptr, ptr + 20), NumPut(0x6850, ptr + 24, "UShort")
            NumPut(Handle, ptr + 26), NumPut(0x68, ptr + 30, "UChar")
            NumPut(Msg, ptr + 31, "UInt"), NumPut(0x68, ptr + 35, "UChar")
            NumPut(hWnd, ptr + 36), NumPut(0x15FF, ptr + 40, "UShort")
            NumPut(ptr+4, ptr + 42), NumPut(0xC2, ptr + 46, "UChar"), NumPut(4, ptr + 47, "UShort")
         }
         else  {
            NumPut(0x53, ptr + 16, "UChar")
            NumPut(0x20EC8348, ptr + 17, "UInt"), NumPut(0xBACB8948, ptr + 21, "UInt")
            NumPut(Timeout, ptr + 25, "UInt"), NumPut(0xB948, ptr + 29, "UShort")
            NumPut(Handle, ptr + 31), NumPut(0x15FF, ptr + 39, "UShort")
            NumPut(-45, ptr + 41, "UInt"), NumPut(0xB849, ptr + 45, "UShort")
            NumPut(Handle, ptr + 47), NumPut(0xBA, ptr + 55, "UChar")
            NumPut(Msg, ptr + 56, "UInt"), NumPut(0xB948, ptr + 60, "UShort")
            NumPut(hWnd, ptr + 62), NumPut(0xC18941, ptr + 70, "UInt")
            NumPut(0x15FF, ptr + 73, "UShort"), NumPut(-71, ptr + 75, "UInt")
            NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B, ptr + 83, "UShort")
         }
         Return ptr + A_PtrSize*2
      }
   }
}
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

29 May 2020, 11:45

Hello teadrinker. I have comments,

1) I'm still concerned about _Event.__delete, setting the event doesn't guarantee that the wait function has stopped waiting for it when you close the handle, after the sleep. Regardless, it shouldn't be __delete's job to set the event, at least not if the class is for general use. Instead I propose a _Event.Set() method, which you call in FileMonitoring.__delete(), instead of this.Event := "".
2) Similarly, _Thread.__delete shouldn't wait for the thread to finish. It is fine to close the thread handle before the thread has finished, the thread lives until all handles has been closed and it has finished. Instead, define _Thread.wait(). I would call this before overwriting the thread handle in the message monitor (this is most likely never needed but one could theorize about spectacular cases). More importantly, you must call _Thread.wait() before you free the startAddress, do it in _EventSignal.clear().

Hehe, I hope that is all, cheers.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

29 May 2020, 21:48

@Helgef
Appreciate your help, added what you suggested.

Code: Select all

#NoEnv
SetBatchLines -1

FILE_NOTIFY_CHANGE_FILE_NAME  := 0x1
FILE_NOTIFY_CHANGE_DIR_NAME   := 0x2
FILE_NOTIFY_CHANGE_ATTRIBUTES := 0x4
FILE_NOTIFY_CHANGE_SIZE       := 0x8

notifyFilter := FILE_NOTIFY_CHANGE_FILE_NAME
              | FILE_NOTIFY_CHANGE_DIR_NAME
              | FILE_NOTIFY_CHANGE_ATTRIBUTES
              | FILE_NOTIFY_CHANGE_SIZE

folderPath1 := A_Desktop
folderPath2 := A_ScriptDir
              
Inst1 := new FileMonitoring(folderPath1, notifyFilter, "OnDirectoryChanged1", true)
Inst2 := new FileMonitoring(folderPath2, notifyFilter, "OnDirectoryChanged2")
Return

OnDirectoryChanged1(filePath, event) {
   MsgBox, % ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][event] ": " filePath
}

OnDirectoryChanged2(filePath, event) {
   MsgBox, % ["Added", "Removed", "Modified", "Renamed, old name", "Renamed, new name"][event] ": " filePath
}

class FileMonitoring
{
   __New(folderPath, notifyFilter, UserFunc, watchSubtree := false) {
      this.Event := new this._Event()
      this.SetCapacity("buffer", 1024)
      pBuffer := this.GetAddress("buffer")
      this.SetCapacity("overlapped", A_PtrSize*3 + 8)
      this.pOverlapped := this.GetAddress("overlapped")
      this.Directory := new this._ReadDirectoryChanges( folderPath, notifyFilter, watchSubtree
                                                      , pBuffer, this.pOverlapped, this.Event.handle )
      this.EventSignal := new this._EventSignal(this.Directory, this.Event.handle, pBuffer, UserFunc)
      this.Directory.Read()
   }
   
   __Delete() {
      DllCall("CancelIoEx", "Ptr", this.Directory.handle, "Ptr", this.pOverlapped)
      this.Event.Set()
      this.EventSignal.Clear()
      this.Directory.Clear()
      this.SetCapacity("buffer", 0)
      this.buffer := ""
   }
   
   class _Event {
      __New() {
         this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 0, "Int", 0, "Ptr")
      }
      Set() {
         DllCall("SetEvent", "Ptr", this.handle)
      }
      __Delete() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _ReadDirectoryChanges {
      __New(dirPath, notifyFilter, watchSubtree, pBuffer, pOverlapped, hEvent) {
         static OPEN_EXISTING := 3
              , access := (FILE_SHARE_READ := 1) | (FILE_SHARE_WRITE := 2)
              , flags := (FILE_FLAG_OVERLAPPED := 0x40000000) | (FILE_FLAG_BACKUP_SEMANTICS := 0x2000000)
              
         for k, v in ["notifyFilter", "watchSubtree", "pBuffer", "pOverlapped", "hEvent"]
            this[v] := %v%
         this.handle := DllCall("CreateFile", "Str", dirPath, "UInt", 1, "UInt", access, "Int", 0
                                            , "UInt", OPEN_EXISTING, "UInt", flags, "Int", 0, "Ptr")
      }
      
      Read() {
         DllCall("RtlZeroMemory", "Ptr", this.pOverlapped, "Ptr", A_PtrSize*3 + 8)
         NumPut(this.hEvent, this.pOverlapped + A_PtrSize*2 + 8, "Ptr")
         ; there is only a Unicode version of this api
         Return DllCall("ReadDirectoryChangesW", "Ptr", this.handle, "Ptr", this.pBuffer, "UInt", 1024, "UInt", this.watchSubtree
                                               , "UInt", this.notifyFilter, "Ptr", 0, "Ptr", this.pOverlapped, "Ptr", 0)
      }
      
      Clear() {
         DllCall("CloseHandle", "Ptr", this.handle)
      }
   }
   
   class _EventSignal {
      __New(Directory, hEvent, pBuffer, UserFunc) {
         this.WM_EVENTSIGNAL := DllCall("RegisterWindowMessage", "Str", "WM_EVENTSIGNAL")
         for k, v in ["Directory", "hEvent", "pBuffer"]
            this[v] := %v%
         this.UserFunc := IsObject(UserFunc) ? UserFunc : Func(UserFunc)
         this.OnEvent := ObjBindMethod(this, "On_WM_EVENTSIGNAL")
         OnMessage(this.WM_EVENTSIGNAL, this.OnEvent)
         this.startAddress := this.CreateWaitFunc(this.hEvent, A_ScriptHwnd, this.WM_EVENTSIGNAL)
         this.Thread := new this._Thread(this.startAddress)
      }
      
      On_WM_EVENTSIGNAL(wp) {
         if !( wp = this.hEvent
            && DllCall("GetOverlappedResult", "Ptr", this.hEvent, "Ptr", this.pBuffer, "UIntP", written, "UInt", false) )
            Return
         
         addr := this.pBuffer
         offset := 0
         Loop {
            addr += offset
            eventType  := NumGet(addr + 4, "UInt")
            objectName := StrGet(addr + 12, NumGet(addr + 8, "UInt")//2, "UTF-16") ; always in Unicode
            timer := this.UserFunc.Bind(objectName, eventType)
            SetTimer, % timer, -10
         } until !offset := NumGet(addr + 0, "UInt")
         this.Thread.Wait()
         this.Thread := new this._Thread(this.startAddress)
         this.Directory.Read()
      }

      CreateWaitFunc(hEvent, hWnd, msg, timeout := -1) {
         params := ["UInt", MEM_COMMIT := 0x1000, "UInt", PAGE_EXECUTE_READWRITE := 0x40, "Ptr"]
         ptr := DllCall("VirtualAlloc", "Ptr", 0, "Ptr", A_PtrSize = 4 ? 49 : 85, params*)
         hModule      := DllCall("GetModuleHandle", "Str", "kernel32.dll", "Ptr")
         pWaitForObj  := DllCall("GetProcAddress" , "Ptr", hModule, "AStr", "WaitForSingleObject", "Ptr")
         hModule      := DllCall("GetModuleHandle", "Str", "user32.dll", "Ptr")
         pPostMessage := DllCall("GetProcAddress" , "Ptr", hModule, "AStr", "PostMessageW", "Ptr")
         NumPut(pWaitForObj , ptr + 0)
         NumPut(pPostMessage, ptr + A_PtrSize)

         if (A_PtrSize = 4) {
            NumPut(0x68   , ptr +  8)
            NumPut(timeout, ptr +  9)          , NumPut(0x68  , ptr + 13)
            NumPut(hEvent , ptr + 14)          , NumPut(0x15FF, ptr + 18)
            NumPut(ptr    , ptr + 20)          , NumPut(0x6850, ptr + 24)
            NumPut(hEvent , ptr + 26)          , NumPut(0x68  , ptr + 30)
            NumPut(msg    , ptr + 31)          , NumPut(0x68  , ptr + 35)
            NumPut(hWnd   , ptr + 36)          , NumPut(0x15FF, ptr + 40)
            NumPut(ptr + 4, ptr + 42)          , NumPut(0xC2  , ptr + 46, "UChar")
            NumPut(4      , ptr + 47, "UShort")
         }
         else {
            NumPut(0x53      , ptr + 16)
            NumPut(0x20EC8348, ptr + 17)        , NumPut(0xBACB8948, ptr + 21)
            NumPut(timeout   , ptr + 25)        , NumPut(0xB948    , ptr + 29)
            NumPut(hEvent    , ptr + 31)        , NumPut(0x15FF    , ptr + 39)
            NumPut(-45       , ptr + 41)        , NumPut(0xB849    , ptr + 45)
            NumPut(hEvent    , ptr + 47)        , NumPut(0xBA      , ptr + 55)
            NumPut(msg       , ptr + 56)        , NumPut(0xB948    , ptr + 60)
            NumPut(hWnd      , ptr + 62)        , NumPut(0xC18941  , ptr + 70)
            NumPut(0x15FF    , ptr + 73)        , NumPut(-71       , ptr + 75)
            NumPut(0x20C48348, ptr + 79, "UInt"), NumPut(0xC35B    , ptr + 83, "UShort")
         }
         Return ptr + A_PtrSize*2
      }
      
      class _Thread {
         __New(startAddress) {
            if !this.handle := DllCall("CreateThread", "Int", 0, "Int", 0, "Ptr", startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
               throw Exception("Failed to create thread.`nError code: " . A_LastError)
         }
         Wait() {
            DllCall("WaitForSingleObject", "Ptr", this.handle, "Int", -1)
         }
         __Delete() {
            DllCall("CloseHandle", "Ptr", this.handle)
         }
      }
      
      Clear() {
         this.Thread.Wait()
         OnMessage(this.WM_EVENTSIGNAL, this.OnEvent, 0)
         this.OnEvent := ""
         DllCall("VirtualFree", "Ptr", this.startAddress - A_PtrSize*2, "Ptr", A_PtrSize = 4 ? 49 : 85, "UInt", MEM_DECOMMIT := 0x4000)
      }
   }
}
Last edited by teadrinker on 08 Feb 2022, 13:39, edited 3 times in total.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

29 May 2020, 23:35

I have comments,
Spoiler
Cheers.
icc2icc
Posts: 18
Joined: 04 Aug 2021, 21:24

Re: Detect file change

30 Jan 2022, 02:37

teadrinker wrote:
29 May 2020, 21:48
@Helgef
Appreciate your help, added what you suggested.
2 comments
1) line 72 (MsgBox, % this.watchSubtree) you forgot to remove
2) I got errors when I tried to clear my pointer to the class
but when I added back this.Event := "" to the __delete section it worked

please tell me where I'm wrong
Last edited by icc2icc on 30 Jan 2022, 19:50, edited 1 time in total.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

30 Jan 2022, 04:29

icc2icc wrote: 1) line 72 (MsgBox, % this.watchSubtree) you forgot to remove
Thanks, I've edited my post.
icc2icc wrote: 2) I got errors when I tried to clear my pointer to the class
Can you show the code that demonstrates the issue?
icc2icc
Posts: 18
Joined: 04 Aug 2021, 21:24

Re: Detect file change

30 Jan 2022, 20:05

I just used a timer to run this line
Inst2 := new FileMonitoring(folderPath2, notifyFilter, "OnDirectoryChanged2")
every few seconds to refresh it as it gets stuck when editing a large amount of files in the same second
unfortunately I notice now that I get the error even when adding back this.Event := "" but much less frequent
I can I handle huge amount of file editing monitoring?
Attachments
2.PNG
2.PNG (16.6 KiB) Viewed 1447 times
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

30 Jan 2022, 20:40

Can't reproduce such error.
icc2icc wrote:but when I added back this.Event := "" to the __delete section it worked
Actually it's unnecessary, this.Event is freed anyway.
If my class does not work perfectly for you, you can try another approach.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: Google [Bot], sbrady19 and 133 guests