Detect file change Topic is solved

Get help with using AutoHotkey (v1.1 and older) and its commands and hotkeys
Meroveus
Posts: 44
Joined: 23 May 2016, 17:38

Detect file change

21 May 2020, 11:35

The plan is to open a file for reading
Read the file and extract the last occurrence of a specific string using regexp
Save the position of the file pointer
Since it is the log file of another application, it will be changed by the other program.
When that happens, I want to be notified that it has happened, then read from the file pointer to the current EOF

In the olden days, I could use a callback function
I see AHK has RegisterCallback()

I presume that it would involve the FindFirstChangeNotification API
https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstchangenotificationa

Has anyone done this before?

As an alternative, I could set up a timer and monitor the file size ever few seconds, but I'd like to get to know call back functions, and this seems like an ideal place to use one.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Detect file change

21 May 2020, 13:19

seems like an ideal place to use one.
only if ure ok with the whole script being left stuck waiting in a nonsignaled state the majority of the time. see https://docs.microsoft.com/en-us/windows/win32/fileio/obtaining-directory-change-notifications

just check for changes with SetTimer + ahk functions. this api is meant for multithreaded applications, which ahk isnt
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change  Topic is solved

21 May 2020, 13:31

This could be done with WMI:

Code: Select all

#Persistent
FolderPath := "D:\Downloads"   ; set the folder to watch
interval := 0.5                 ; set the interval

SetFileMonitoring(FolderPath, interval)

SetFileMonitoring(FolderPath, interval)  {
   static winmgmts := ComObjGet("winmgmts:"), createSink
   
   SplitPath, FolderPath,,,,, Drive
   Folder := RegExReplace(FolderPath, "[A-Z]:\\|((?<!\\)\\(?!\\)|(?<!\\)$)", "\\")

   ComObjConnect(createSink := ComObjCreate("WbemScripting.SWbemSink"), "FileEvent_")

   winmgmts.ExecNotificationQueryAsync(createSink
      , "Select * From __InstanceOperationEvent"
      . " within " interval
      . " Where Targetinstance Isa 'CIM_DataFile'"
      . " And TargetInstance.Drive='" Drive "'"
      . " And TargetInstance.Path='" Folder "'")
}
   
FileEvent_OnObjectReady(objEvent)
{
   if (objEvent.Path_.Class = "__InstanceModificationEvent")
      TrayTip, The file has been changed:, % RegExReplace(objEvent.TargetInstance.Name, ".*\\")
}
Return
Meroveus
Posts: 44
Joined: 23 May 2016, 17:38

Re: Detect file change

21 May 2020, 15:48

Boom!
That is what I had been remembering...
User avatar
flyingDman
Posts: 2791
Joined: 29 Sep 2013, 19:01

Re: Detect file change

21 May 2020, 16:12

This gives me a "quota violation error". On a smaller folder it works fine. What is the file count limit?
14.3 & 1.3.7
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

21 May 2020, 21:42

An implementation based on ReadDirectoryChanges:

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
   Critical
   if (msg = WM_ReadDirectoryChanges && Event := EventList[wParam]) {
      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", 32)
      NumPut(this.handle, this.GetAddress("overlapped"), 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 !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 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 := 0x2000000
      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
}
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Detect file change

21 May 2020, 23:56

@teadrinker

Code: Select all

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 does this do? post the source

and

Code: Select all

DllCall("CreateThread"
is this safe in ahk now? last time i tried it my script kept crashing occasionally
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

22 May 2020, 00:08

swagfag wrote: what does this do?
This function creates the machine code for WaitForSingleObject and SendMessageW to launch them in a separated thread.
swagfag wrote: is this safe in ahk now?
Yes, for me this code works without issues.
swagfag wrote: post the source
I can't, since it was not written by me.
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

22 May 2020, 02:07

This function creates the machine code for WaitForSingleObject and SendMessageW to launch them in a separated thread.
This is perfectly safe to do, but that doesn't mean the code is safe,
WaitForSingleObject - hHandle wrote: If this handle is closed while the wait is still pending, the function's behavior is undefined.
It is not very clear if this is handled, I guess it is not.
CreateThread wrote: The thread object remains in the system until the thread has terminated and all handles to it have been closed through a call to CloseHandle
It seems you create a new thread for every time an event occurs, and not closing the old thread handle.

Code: Select all

this.SetCapacity("overlapped", 32)
This does not zero initialise the memory, and,
OVERLAPPED structure wrote: Any unused members of this structure should always be initialized to zero before the structure is used in a function call. Otherwise, the function may fail and return ERROR_INVALID_PARAMETER.
ReadDirectoryChangesW - lpBuffer wrote: This buffer is filled either synchronously or asynchronously
In your case, it is filled asynchronously, and I cannot see any code which syncs the reading of the result, this is not safe.

Also, since you make an explicit call to the msg monitor callback, DirectoryChangesEventListener(-1, 0, 0), you should avoid using critical in the message monitor callback, else you set this as the default for all threads (since you make this call in the auto-exec section).

Cheers.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Detect file change

22 May 2020, 02:52

so its just calling

Code: Select all

WaitForSingleObject(Handle, Timeout);
SendMessageW(hWnd, Msg, Handle, ???);
though i cant quite make out what its passing in lParam. 0? 0x50?
and what/if it returns
@Helgef are u saying that starting threads with CreateThread is safe or that WaitForSingleObject is safe?
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

22 May 2020, 03:31

The message monitor function doesn't doesn't use lParam. It doesn't have a fixed return value, it will return whatever sendmessage returns.
are u saying that starting threads with CreateThread is safe or that WaitForSingleObject is safe?
Both are safe unless you do anything unsafe with them.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

22 May 2020, 03:40

@Helgef
Thanks for clarifications, I've added some improvements (RtlZeroMemory, TerminateThread and CloseHandle), is it enough?
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

22 May 2020, 03:55

I've added some improvements (RtlZeroMemory, TerminateThread and CloseHandle), is it enough?
Hard to say without seeing the code ;)

Code: Select all

action := Event.Proc.GetEventType()
name := Event.Proc.GetObjectName()
Did you handle this? You are reading from the buffer which is filled asynch.
ReadDirectoryChangesW wrote: For asynchronous completion, you can receive notification in one of three ways:
Cheers.
teadrinker
Posts: 4309
Joined: 29 Mar 2015, 09:41
Contact:

Re: Detect file change

22 May 2020, 04:16

Helgef wrote: Did you handle this? You are reading from the buffer which is filled asynch.
Not completely understand. Now reading occurs when a message from the thread is recieved. I can implement a completion routine, in that case messaging is not needed? What the issues may the current way cause?
Sorry, forgot to post the code:

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]) {
      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
}
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

22 May 2020, 08:15

Hi.
TerminateThread wrote: TerminateThread is a dangerous function that should only be used in the most extreme cases.
For example, TerminateThread can result in the following problems:
If the target thread is executing certain kernel32 calls when it is terminated, the kernel32 state for the thread's process could be inconsistent.
Xp users should not use it at all.
Now reading occurs when a message from the thread is recieved.
I don't see that the documentation guarantees that the buffer has been formatted at this stage, although it evidently can be. It seems like it would be trivial to call GetOverlappedResult in the message callback, then you can also verify that buffer really has been written to.
I can implement a completion routine, in that case messaging is not needed?
This would also be a possibility.
What the issues may the current way cause?
The result of concurrent read and write operations is undefined, anything can happen.

Cheers.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Detect file change

22 May 2020, 10:14

Helgef wrote:
22 May 2020, 03:31
Both(CreateThread, WaitForSingleObject) are safe unless you do anything unsafe with them.
ive tried running this code with ahk x64

Code: Select all

#NoEnv
; SetBatchLines -1

DllCall("CreateThread", "Ptr", 0, "Ptr", 0, "Ptr", RegisterCallback("ThreadProc"), "Ptr", 0, "UInt", 0, "UInt*", lpThreadId, "Ptr")
ThreadProc(lpParameter) {
	i := 0

	Loop 5
		ToolTip % ++i

	MsgBox % i 
}

Esc::ExitApp
with default SetBatchLines a ToolTip "1" is shown, the loop never exits and it never gets to the msgbox even
with SetBatchLines -1 sometimes:
  • the same thing happens: a ToolTip "1" is shown, the loop never exits and it never gets to the msgbox even
  • it counts up to 5 very fast(presumably also showing incrementing tooltips in between), a ToolTip "5" is shown and a MsgBox "5" is shown
  • it counts up to 6 very fast(presumably also showing incrementing tooltips in between), a ToolTip "6" is shown and a MsgBox "6" is shown
CreateThread doesnt seem to be working very well
and if i add a Sleep inside the loop, it gets stuck on ToolTip "1" all the time in every case
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

22 May 2020, 10:18

That would be an example where you do something unsafe with createthread.
swagfag
Posts: 6222
Joined: 11 Jan 2017, 17:59

Re: Detect file change

22 May 2020, 11:08

:roll: so "its safe until its not"
ill take that as "regular ahk functions + RegisterCallback = unsafe"
@teadrinker
i think the source is

Code: Select all

void __fastcall ThreadProc(LPVOID lpParameter) {
    DWORD WaitEvent = WaitForSingleObject(Handle, Timeout);
    SendMessageW(hWnd, Msg, (WPARAM)Handle, WaitEvent);
}
except with all parameters(save for WaitEvent) already hardcoded in
and still not sure whether the return type is correct
Helgef
Posts: 4709
Joined: 17 Jul 2016, 01:02
Contact:

Re: Detect file change

22 May 2020, 12:08

swagfag wrote:
22 May 2020, 11:08
:roll: so "its safe until its not"
Exactly ;). Rule of thumbs, shared mutable state, bad :thumbdown:, everything else, fine :thumbup:.
ill take that as "regular ahk functions + RegisterCallback = unsafe"
Right again, think about what you discovered in Confirmed bug with #if and Hotkey, If , command parameters ending up in a static variable, causing issues with ahk's psuedo threads sharing states, clearly you cannot run multiple threads with such a design.
i think the source is

Code: Select all

void __fastcall ThreadProc(LPVOID lpParameter) {
    DWORD WaitEvent = WaitForSingleObject(Handle, Timeout);
    SendMessageW(hWnd, Msg, (WPARAM)Handle, WaitEvent);
}
That is possible, I didn't disassemble it, but I verified (with dllcall) that it returns the result from SendMessage, which I suppose can be simply because its return value is left in the return register. Note,
ThreadProc callback function wrote: Do not declare this callback function with a void return type and cast the function pointer to LPTHREAD_START_ROUTINE when creating the thread. Code that does this is common, but it can crash on 64-bit Windows.
Cheers.

Return to “Ask for Help (v1)”

Who is online

Users browsing this forum: gongnl, Google [Bot], marypoppins_1 and 117 guests