Page 2 of 3

Re: Detect file change

Posted: 22 May 2020, 12:40
by swagfag
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);
}

Re: Detect file change

Posted: 22 May 2020, 14:11
by teadrinker
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?

Re: Detect file change

Posted: 23 May 2020, 02:19
by Helgef
The best way to terminate a thread is to let it finish. You can signal the event (SetEvent) so the wait (WaitForSingleObject) end.

Cheers.

Re: Detect file change

Posted: 23 May 2020, 20:40
by teadrinker
Thanks! Damn, I had to guess myself! :headwall: I will post the code later.

Re: Detect file change

Posted: 23 May 2020, 21:25
by malcev
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

Re: Detect file change

Posted: 23 May 2020, 23:25
by Helgef
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.

Re: Detect file change

Posted: 26 May 2020, 13:45
by teadrinker
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
      }
   }
}

Re: Detect file change

Posted: 28 May 2020, 10:07
by Helgef
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.

Re: Detect file change

Posted: 28 May 2020, 10:37
by teadrinker
Thanks!
Aha, you used the timer instead of a wait function. :)

Re: Detect file change

Posted: 28 May 2020, 11:56
by Helgef
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.

Re: Detect file change

Posted: 28 May 2020, 12:56
by teadrinker
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.

Re: Detect file change

Posted: 29 May 2020, 00:32
by Helgef
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:.

Re: Detect file change

Posted: 29 May 2020, 07:14
by teadrinker
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
      }
   }
}

Re: Detect file change

Posted: 29 May 2020, 11:45
by Helgef
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.

Re: Detect file change

Posted: 29 May 2020, 21:48
by teadrinker
@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)
      }
   }
}

Re: Detect file change

Posted: 29 May 2020, 23:35
by Helgef
I have comments,
Spoiler
Cheers.

Re: Detect file change

Posted: 30 Jan 2022, 02:37
by icc2icc
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

Re: Detect file change

Posted: 30 Jan 2022, 04:29
by teadrinker
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?

Re: Detect file change

Posted: 30 Jan 2022, 20:05
by icc2icc
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?

Re: Detect file change

Posted: 30 Jan 2022, 20:40
by teadrinker
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.