1 (изменено: Александр_, 2012-01-28 17:51:18)

Тема: AHK: асинхронный вызов Wait-функции

Во вчерашней теме мы выяснили, как определить разрядность скрипта, OS и архитектуру процессора. В этот раз предлагаю попытаться описать более-менее универсальный метод вызова ожидающих функций. Пока я рассмотрю только самый простой случай, когда нужно дождаться какого-нибудь события(список см. тут) и выполнить действие. Похожий код уже не раз был на форуме, эта функция выделяет память и записывает туда код функции потока, которую можно вызвать функцией CreateThread, сабж для x86:

GetProcAddrX86(Handle, hWnd, Msg, Timeout=-1)
{
   VarSetCapacity(FuncName, 20, 0)
   ptr := DllCall("VirtualAlloc", "ptr", 0, "ptr", 49, "uint", 0x1000, "uint", 0x40, "ptr")
   StrPut("WaitForSingleObject", &FuncName, 20, "CP1250")
   NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "kernel32.dll"), "ptr", &FuncName, "ptr"), 0|ptr, 0, "ptr")
   StrPut("SendMessageW", &FuncName, 20, "CP1250")
   NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32.dll"), "ptr", &FuncName, "ptr"), 0|ptr, 4, "ptr")
   NumPut(0x68, 0|ptr, 8, "uchar")
   NumPut(Timeout, 0|ptr, 9, "uint")
   NumPut(0x68, 0|ptr, 13, "uchar")
   NumPut(Handle, 0|ptr, 14, "ptr")
   NumPut(0x15FF, 0|ptr, 18, "ushort")
   NumPut(0|ptr, 0|ptr, 20, "ptr")
   NumPut(0x50, 0|ptr, 24, "uchar")
   NumPut(0x68, 0|ptr, 25, "uchar")
   NumPut(Handle, 0|ptr, 26, "ptr")
   NumPut(0x68, 0|ptr, 30, "uchar")
   NumPut(Msg, 0|ptr, 31, "uint")
   NumPut(0x68, 0|ptr, 35, "uchar")
   NumPut(hWnd, 0|ptr, 36, "ptr")
   NumPut(0x15FF, 0|ptr, 40, "ushort")
   NumPut(0|ptr+4, 0|ptr, 42, "ptr")
   NumPut(0xC2, 0|ptr, 46, "uchar")
   NumPut(4, 0|ptr, 47, "ushort")
   return ptr+8
}

и её аналог для x64(а вот его ещё не было ):

GetProcAddrX64(Handle, hWnd, Msg, Timeout=-1)
{
   VarSetCapacity(FuncName, 20, 0)
   ptr := DllCall("VirtualAlloc", "ptr", 0, "ptr", 85, "uint", 0x1000, "uint", 0x40, "ptr")
   StrPut("WaitForSingleObject", &FuncName, 20, "CP1250")
   NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "kernel32.dll"), "ptr", &FuncName, "ptr"), 0|ptr, 0, "ptr")
   StrPut("SendMessageW", &FuncName, 20, "CP1250")
   NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32.dll"), "ptr", &FuncName, "ptr"), 0|ptr, 8, "ptr")
   NumPut(0x53, 0|ptr, 16, "uchar")
   NumPut(0x20EC8348, 0|ptr, 17, "uint")
   NumPut(0xBACB8948, 0|ptr, 21, "uint")
   NumPut(Timeout, 0|ptr, 25, "uint")
   NumPut(0xB948, 0|ptr, 29, "ushort")
   NumPut(Handle, 0|ptr, 31, "ptr")
   NumPut(0x15FF, 0|ptr, 39, "ushort")
   NumPut(-45, 0|ptr, 41, "uint")
   NumPut(0xB849, 0|ptr, 45, "ushort")
   NumPut(Handle, 0|ptr, 47, "ptr")
   NumPut(0xBA, 0|ptr, 55, "uchar")
   NumPut(Msg, 0|ptr, 56, "uint")
   NumPut(0xB948, 0|ptr, 60, "ushort")
   NumPut(hWnd, 0|ptr, 62, "ptr")
   NumPut(0xC18941, 0|ptr, 70, "uint")
   NumPut(0x15FF, 0|ptr, 73, "ushort")
   NumPut(-71, 0|ptr, 75, "uint")
   NumPut(0x20C48348, 0|ptr, 79, "uint")
   NumPut(0x5B, 0|ptr, 83, "uchar")
   NumPut(0xC3, 0|ptr, 84, "uchar")
   return ptr+16
}

Функции очень похожи и их не сложно объединить в одну- они принимают 4 параметра:
1) Хендл объекта, который будем ждать
2) идентификатор окна, которому нужно будет послать сообщение
3) сообщение, которое будем посылать
4) время в миллисекундах, которое отведено на ожидание(по умолчанию -1, т.е. неограниченное время)
lParam полученного сообщения будет содержать результат выполнения WaitForSingleObject, т.е. 0(событие произошло), 0x102(время ожидания вышло, а событие так и не произошло) или 0xFFFFFFFF(ошибка при вызове).
В wParam будет переданный в первом параметре хендл.
Пример использования:

r := A_PtrSize = 4 ? 86 : 64
Gui, Add, ListView, r20 w480, wParam(хендл)|lParam|msg
Gui, Show
Gui, +LastFound
hWnd := WinExist()
OnMessage(0x401, "DlgProc")
SetFormat, Integer, h
Run, calc.exe, , , PID
hProc := DllCall("OpenProcess", "uint", 0x100000, "uint", 0, "uint", PID, "ptr")
LV_Add("", hProc)
x := GetProcAddrX%r%(hProc, hWnd, 0x401, 5000)
DllCall("CreateThread", "ptr", 0, "ptr", 0, "ptr", 0|x, "ptr", 0, "uint", 0, "ptr", 0)
Run, calc.exe, , , PID
hProc := DllCall("OpenProcess", "uint", 0x100000, "uint", 0, "uint", PID, "ptr")
LV_Add("", hProc)
x := GetProcAddrX%r%(hProc, hWnd, 0x401, -1)
DllCall("CreateThread", "ptr", 0, "ptr", 0, "ptr", 0|x, "ptr", 0, "uint", 0, "ptr", 0)
return

DlgProc(wParam, lParam, msg, hWnd)
{
   LV_Add("", wParam, lParam, msg)
}

GuiClose:
   ExitApp

В результате выполнения скрипта мы увидим в ListView оба хендла и запущенные калькуляторы. Если в течение пяти секунд не закрыть первый калькулятор, то придёт сообщение с хендлом первого калькулятора и lParam=0x102.
Теперь что нужно учесть:
1) GetProcAddrX64 будет работать только на x64, на IA64 не будет.
2) VirtualAlloc выделяет память постранично. Т.е. даже если мы выделяем 1 байт, то выделена будет вся страница(обычно 4KB). Это может создать приличную утечку памяти(теоретически размер страницы может достигать 4MB). Поэтому ненужные обработчики следует удалять через VirtualFree. На x86 можно выделять память из кучи(LocalAlloc) и тогда серьёзной утечки не будет, однако на x64 по-умолчанию нельзя выполнять этот код.
Проблема легко решается, если мы знаем, сколько всего может быть обработчиков- тогда можно сразу выделить достаточно памяти и писать обработчики друг за другом(на одной странице в 4KB уместится 59 x64-обработчиков или 99 x86-обработчиков). Пример такой функции:

GetWaitFuncAddr(Handle, hWnd, Msg, Timeout=-1)
{
   global __PageSize__, __Offset__, __Ptr__
   if(!__Offset__)
   {
      VarSetCapacity(FuncName, 0x30, 0)
      __Ptr__ := DllCall("VirtualAlloc", "ptr", 0, "ptr", 2*A_PtrSize+(A_PtrSize = 4 ? 41 : 69)*60, "uint", 0x1000, "uint", 0x40, "ptr")
      DllCall("VirtualQuery", "ptr", 0|__Ptr__, "ptr", &FuncName, "ptr", 0x30)
      __PageSize__ := NumGet(&FuncName, 3*A_PtrSize, "ptr")
      StrPut("WaitForSingleObject", &FuncName, 0x30, "CP1250")
      NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "kernel32.dll"), "ptr", &FuncName, "ptr"), 0|__Ptr__, 0, "ptr")
      StrPut("SendMessageW", &FuncName, 0x30, "CP1250")
      NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32.dll"), "ptr", &FuncName, "ptr"), 0|__Ptr__, A_PtrSize, "ptr")
	  __Offset__ := 2*A_PtrSize
   }
   ptr := __Ptr__+__Offset__
   if(A_PtrSize=4)
   {
      if(__PageSize__-__Offset__<41)
	     return -1
      NumPut(0x68, 0|ptr, 0, "uchar")
      NumPut(Timeout, 0|ptr, 1, "uint")
      NumPut(0x68, 0|ptr, 5, "uchar")
      NumPut(Handle, 0|ptr, 6, "ptr")
      NumPut(0x15FF, 0|ptr, 10, "ushort")
      NumPut(0|__Ptr__, 0|ptr, 12, "ptr")
      NumPut(0x50, 0|ptr, 16, "uchar")
      NumPut(0x68, 0|ptr, 17, "uchar")
      NumPut(Handle, 0|ptr, 18, "ptr")
      NumPut(0x68, 0|ptr, 22, "uchar")
      NumPut(Msg, 0|ptr, 23, "uint")
      NumPut(0x68, 0|ptr, 27, "uchar")
      NumPut(hWnd, 0|ptr, 28, "ptr")
      NumPut(0x15FF, 0|ptr, 32, "ushort")
      NumPut(0|__Ptr__+4, 0|ptr, 34, "ptr")
      NumPut(0xC2, 0|ptr, 38, "uchar")
      NumPut(4, 0|ptr, 39, "ushort")
	  __Offset__+=41
   }
   else
   {
      if(__PageSize__-__Offset__<69)
	     return -1
	  NumPut(0x53, 0|ptr, 0, "uchar")
      NumPut(0x20EC8348, 0|ptr, 1, "uint")
      NumPut(0xBACB8948, 0|ptr, 5, "uint")
      NumPut(Timeout, 0|ptr, 9, "uint")
      NumPut(0xB948, 0|ptr, 13, "ushort")
      NumPut(Handle, 0|ptr, 15, "ptr")
      NumPut(0x15FF, 0|ptr, 23, "ushort")
      NumPut(-29-__Offset__, 0|ptr, 25, "uint")
      NumPut(0xB849, 0|ptr, 29, "ushort")
      NumPut(Handle, 0|ptr, 31, "ptr")
      NumPut(0xBA, 0|ptr, 39, "uchar")
      NumPut(Msg, 0|ptr, 40, "uint")
      NumPut(0xB948, 0|ptr, 44, "ushort")
      NumPut(hWnd, 0|ptr, 46, "ptr")
      NumPut(0xC18941, 0|ptr, 54, "uint")
      NumPut(0x15FF, 0|ptr, 57, "ushort")
      NumPut(-55-__Offset__, 0|ptr, 59, "uint")
      NumPut(0x20C48348, 0|ptr, 63, "uint")
      NumPut(0x5B, 0|ptr, 67, "uchar")
      NumPut(0xC3, 0|ptr, 68, "uchar")
	  __Offset__+=69
   }
   return ptr
}

В седьмой строке VirtualAlloc вызывается для 60 обработчиков(2*A_PtrSize+(A_PtrSize = 4 ? 41 : 69)*60). Пример запуска 60 калькуляторов:

Gui, Add, ListView, r20 w480, wParam|lParam|msg
Gui, Show
Gui, +LastFound
hWnd := WinExist()
OnMessage(0x401, "DlgProc", 30)
SetFormat, Integer, h
loop 60
{
   Run, calc.exe, , , PID
   hProc := DllCall("OpenProcess", "uint", 0x100000, "uint", 0, "uint", PID, "ptr")
   x := GetWaitFuncAddr(hProc, hWnd, 0x401, -1)
   if(x!=-1)
      DllCall("CreateThread", "ptr", 0, "ptr", 0, "ptr", 0|x, "ptr", 0, "uint", 0, "ptr", 0)
   else
      LV_Add("", "error", "error", "error")
   Sleep 100
}
return

DlgProc(wParam, lParam, msg, hWnd)
{
   LV_Add("", wParam, lParam, msg)
   DllCall("CloseHandle", "ptr", wParam)
}

GuiClose:
   ExitApp

Обращаю внимание на то, что AHK не позволяет запускать обработчик оконных сообщений больше чем в 30-ти потоках(да и их не выдаёт на сравнительно слабых процессорах). Поэтому если закрыть 60 калькуляторов одновременно, то часть уведомлений может быть проигнорирована AHK.
Понимаю, что тема не раскрыта, но думаю пока хватит(устал писАть) . Замечания и дополнения приветствуются.

2 (изменено: Александр_, 2012-06-27 15:27:21)

Re: AHK: асинхронный вызов Wait-функции

Более продвинутый вариант- две функции CreateWaitFunc для создания функции потока и FreeWaitFunc для её удаления. Суть- каждая функция создаётся в уже выделенной памяти, если функция удаляется, то её адрес помещается в специальную очередь и при необходимости может быть использован повторно. Если вся ранее выделенная память занята и очередь удалённых функций пуста, то выделяется ещё одна страница. В коде использованы объекты, поэтому на обычном AHK будет сложно написать аналог.

FreeWaitFunc(ptr)
{
   global __Free__
   __Free__.Insert(ptr)
}

CreateWaitFunc(Handle, hWnd, Msg, Timeout=-1)
{
   global __PageSize__, __Offset__, __Ptr__, __PageNum__, __Free__
   if((!__Offset__)||((__PageSize__-__Offset__<(A_PtrSize = 4 ? 49 : 85))&&(!__Free__.MaxIndex())))
   {
      if(!__Ptr__)
	  {
	     __Ptr__ := Array()
		 __Free__ := Array()
	  }
      __PageNum__++
      VarSetCapacity(FuncName, 0x30, 0)
      __Ptr__[__PageNum__] := DllCall("VirtualAlloc", "ptr", 0, "ptr", 1, "uint", 0x1000, "uint", 0x40, "ptr")
      DllCall("VirtualQuery", "ptr", 0|__Ptr__[__PageNum__], "ptr", &FuncName, "ptr", 0x30)
      __PageSize__ := NumGet(&FuncName, 3*A_PtrSize, "ptr")
      StrPut("WaitForSingleObject", &FuncName, 0x30, "CP1250")
      NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "kernel32.dll"), "ptr", &FuncName, "ptr"), 0|__Ptr__[__PageNum__], 0, "ptr")
      StrPut("PostMessageW", &FuncName, 0x30, "CP1250")
      NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32.dll"), "ptr", &FuncName, "ptr"), 0|__Ptr__[__PageNum__], A_PtrSize, "ptr")
	  __Offset__ := 2*A_PtrSize
   }
   if(!__Free__.MaxIndex())
   {
      ptr := __Ptr__[__PageNum__]+__Offset__
	  __Offset__+=(A_PtrSize = 4 ? 49 : 85)
   }
   else
      ptr := __Free__.Remove(1)
   if(A_PtrSize=4)
   {
      NumPut(0x68, 0|ptr, 0, "uchar")
      NumPut(Timeout, 0|ptr, 1, "uint")
      NumPut(0x68, 0|ptr, 5, "uchar")
      NumPut(Handle, 0|ptr, 6, "ptr")
      NumPut(0x15FF, 0|ptr, 10, "ushort")
      NumPut(0|__Ptr__[__PageNum__], 0|ptr, 12, "ptr")
      NumPut(0x50, 0|ptr, 16, "uchar")
      NumPut(0x68, 0|ptr, 17, "uchar")
      NumPut(Handle, 0|ptr, 18, "ptr")
      NumPut(0x68, 0|ptr, 22, "uchar")
      NumPut(Msg, 0|ptr, 23, "uint")
      NumPut(0x68, 0|ptr, 27, "uchar")
      NumPut(hWnd, 0|ptr, 28, "ptr")
      NumPut(0x15FF, 0|ptr, 32, "ushort")
      NumPut(0|__Ptr__[__PageNum__]+4, 0|ptr, 34, "ptr")
      NumPut(0xC2, 0|ptr, 38, "uchar")
      NumPut(4, 0|ptr, 39, "ushort")
   }
   else
   {
	  NumPut(0x53, 0|ptr, 0, "uchar")
      NumPut(0x20EC8348, 0|ptr, 1, "uint")
      NumPut(0xBACB8948, 0|ptr, 5, "uint")
      NumPut(Timeout, 0|ptr, 9, "uint")
      NumPut(0xB948, 0|ptr, 13, "ushort")
      NumPut(Handle, 0|ptr, 15, "ptr")
      NumPut(0x15FF, 0|ptr, 23, "ushort")
      NumPut(-29-(ptr-__Ptr__[__PageNum__]), 0|ptr, 25, "uint")
      NumPut(0xB849, 0|ptr, 29, "ushort")
      NumPut(Handle, 0|ptr, 31, "ptr")
      NumPut(0xBA, 0|ptr, 39, "uchar")
      NumPut(Msg, 0|ptr, 40, "uint")
      NumPut(0xB948, 0|ptr, 44, "ushort")
      NumPut(hWnd, 0|ptr, 46, "ptr")
      NumPut(0xC18941, 0|ptr, 54, "uint")
      NumPut(0x15FF, 0|ptr, 57, "ushort")
      NumPut(-55-(ptr-__Ptr__[__PageNum__]), 0|ptr, 59, "uint")
      NumPut(0x20C48348, 0|ptr, 63, "uint")
      NumPut(0x5B, 0|ptr, 67, "uchar")
      NumPut(0xC3, 0|ptr, 68, "uchar")
   }
   return ptr
}

Пример использования пока не буду приводить, поскольку всё, вроде, и так понятно.

3

Re: AHK: асинхронный вызов Wait-функции

OFF: Впечатляить ©

4

Re: AHK: асинхронный вызов Wait-функции

Понятно, что работает
Черезвычайно интересен был бы пример использования с ReadDirectoryChangesW, подобный варианту 2 из темы

5

Re: AHK: асинхронный вызов Wait-функции

creature.ws пишет:

Черезвычайно интересен был бы пример использования с ReadDirectoryChangesW, подобный варианту 2 из темы

К сожалению сейчас мало свободного времени. Там нужно создать объект "событие" и использовать его, примерно так:

#Persistent
DetectHiddenWindows On
WinGet, hWnd, id, % "ahk_classAutoHotkey ahk_pid" . DllCall("GetCurrentProcessId") ;находим наше окно
OnMessage(0x401, "DlgProc") ;будем получать сообщения 0x401 в функции DlgProc
hDirectory := DllCall("CreateFile", "str", A_ScriptDir, "uint", 1, "uint", 3, "ptr", 0, "uint", 3, "uint", 0x42000000, "ptr", 0, "ptr") ;открываем папку
hEvent := DllCall("CreateEvent", "ptr", 0, "int", 0, "int", 1, "str", "MyEvent", "ptr") ;создаём событие
EventProc := CreateWaitFunc(hEvent, hWnd, 0x401) ;создаём функцию
VarSetCapacity(Buffer, 1024) ;сюда запишутся сведения о событии
VarSetCapacity(BytesReturned, 4) ;сюда размер сведений
VarSetCapacity(Overlapped, 32, 0) ;это структура содержащая наш объект "событие"
NumPut(hEvent, &Overlapped, a_PtrSize*2+8, "ptr") ;записываем этот объект в структуру
DllCall("ReadDirectoryChangesW", "ptr", hDirectory, "ptr", &Buffer, "uint", 1024, "int", 1, "uint", 3, "ptr", &BytesReturned, "ptr", &Overlapped, "ptr", 0) ;начинаем слежку
DllCall("CreateThread", "ptr", 0, "ptr", 0, "ptr", 0|EventProc, "ptr", 0, "uint", 0, "ptr", 0) ;вызываем нашу функцию
return

DlgProc(wParam, lParam, msg, hWnd)
{
   global Buffer
   if(msg=0x401)
      msgbox тут обработка сообщения и при необходимости повторный вызов ReadDirectoryChangesW и CreateThread
}

6

Re: AHK: асинхронный вызов Wait-функции

Пример понятен, суть уловил. (теперь )

Не понимаю, почему DlgProc выполняется дважды при каждом событии, первый раз с hWnd = A_ScriptHWND, второй — hWnd = 0.

7

Re: AHK: асинхронный вызов Wait-функции

creature.ws пишет:

Не понимаю, почему DlgProc выполняется дважды при каждом событии, первый раз с hWnd = A_ScriptHWND, второй — hWnd = 0.

Второго срабатывания быть не должно, вообще сообщений для hWnd = 0 не должно быть, ведь такого окна не существует.

8

Re: AHK: асинхронный вызов Wait-функции

Поспешил с вопросом я. Причина неожиданного поведения в неопределённой ошибке допущенной при редактировании примера. Переписанный «начисто» пример работает как ожидалось.

9 (изменено: creature.ws, 2012-11-10 16:58:12)

Re: AHK: асинхронный вызов Wait-функции

«Дружелюбный к пользователю» пример использования асинхронного вызова ReadDirectoryChangesW:

#NoEnv
SetBatchLines -1

OnDirectoryChanges(A_ScriptDir, "Test0")
OnDirectoryChanges(A_Desktop, "Test1")

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

Test1(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 := {}
    static EventList
    static AHK_ReadDirectoryChanges := DllCall("RegisterWindowMessage", "Str", "AHK_ReadDirectoryChanges", "UInt")

    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.Remove(dirPath)
            return EventList.Remove(eventHandle).UserDefinedProc.Name
    }

    else if IsFunc(procName) {
        if DirList.HasKey(dirPath) && eventHandle := DirList.Remove(dirPath)
            priorProcName := EventList.Remove(eventHandle).UserDefinedProc.Name
        else priorProcName := procName

        if OnMessage(AHK_ReadDirectoryChanges) != "DirectoryChangesEventListener"
            OnMessage(AHK_ReadDirectoryChanges, "DirectoryChangesEventListener", 30)

        Event := new _Event
        Event.Proc := new _AsyncReadDirectoryChanges(dirPath, notifyFilter)
        Event.Wait := new _AsyncWaitFunc(Event.handle, A_ScriptHWND, AHK_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 := {}
    static AHK_ReadDirectoryChanges := DllCall("RegisterWindowMessage", "Str", "AHK_ReadDirectoryChanges", "UInt")

    if (msg = AHK_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.SetCapacity(6)

        if !this.handle := DllCall("CreateEvent", "Int", 0, "Int", 0, "Int", 1, "Int", 0, "Ptr")
            Throw Exception("Failed to create new event.`n`nError code:`t" . A_LastError)

        if !ObjSetCapacity(this, "overlapped", 32)
            Throw Exception("Failed to allocate memory for overlapped structure`n`nErrorLevel:`t" . ErrorLevel)

        if !NumPut(this.handle, 0+ObjGetAddress(this, "overlapped"), A_PtrSize*2+8, "ptr")
            Throw Exception("Failed to create overlapped structure.`n`nErrorLevel:`t" . ErrorLevel)
    }

    __Delete() {
        if !DllCall("CloseHandle", "Ptr", this.handle, "UInt")
            Throw Exception("Failed to close event handle.`n`nError code:`t" . A_LastError)
    }
}

class _AsyncWaitFunc
{
    __New(eventObjHandle, hWnd, msg, timeout := -1) {
        this.SetCapacity(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", 0|this.startAddress, "Int", 0, "UInt", 0, "Int", 0, "Ptr")
                Throw Exception("Failed to create thread.`n`nError code:`t" . A_LastError)

            return threadHandle
        }
    }

    __Delete() {
        FreeWaitFunc(this.startAddress)
    }
}

class _AsyncReadDirectoryChanges
{
    __New(dirPath, notifyFilter) {
        this.SetCapacity(3)
        this.notifyFilter := notifyFilter

        if !this.handle := DllCall("CreateFile", "Str", dirPath, "UInt", 1, "UInt", 3, "Int", 0, "UInt", 3, "UInt", 0x42000000, "Int", 0, "Ptr")
            Throw Exception("Failed to open specified path.`n`nError code:`t" . A_LastError)

        if !ObjSetCapacity(this, "buffer", 1024)
            Throw Exception("Failed to set ReadDirectoryChangesW output buffer size`n`nErrorLevel:`t" . ErrorLevel)
    }

    GetEventType() {
        if !bufferRef := ObjGetAddress(this, "buffer")
            Throw Exception("Failed to get ReadDirectoryChangesW output buffer address`n`nErrorLevel:`t" . ErrorLevel)

        return NumGet(0+bufferRef, 4,  "UInt")
    }

    GetObjectName() {
        if !bufferRef := ObjGetAddress(this, "buffer")
            Throw Exception("Failed to get ReadDirectoryChangesW output buffer address`n`nErrorLevel:`t" . ErrorLevel)

        return StrGet(bufferRef+12, NumGet(0+bufferRef, 8, "UInt")//2)
    }

    __Call(EventObj) {
        if IsObject(EventObj) {
            if !bufferRef := ObjGetAddress(this, "buffer")
                Throw Exception("Failed to get ReadDirectoryChangesW output buffer address`n`nErrorLevel:`t" . ErrorLevel)

            if !overlappedStructRef := ObjGetAddress(EventObj, "overlapped")
                Throw Exception("Failed to get ReadDirectoryChangesW overlapped struct address`n`nErrorLevel:`t" . ErrorLevel)

            return DllCall("ReadDirectoryChangesW"
                , "Ptr", this.handle
                , "Ptr", bufferRef
                , "UInt", 1024
                , "Int", 1
                , "UInt", this.notifyFilter
                , "UInt", 0
                , "Ptr", overlappedStructRef
                , "Int", 0)
        }
    }

    __Delete() {
        if !DllCall("CloseHandle", "Ptr", this.handle, "UInt")
            Throw Exception("Failed to close directory handle.`n`nError code:`t" . A_LastError)
    }

}

FreeWaitFunc(ptr)
{
    global __Free__
    __Free__.Insert(ptr)
}

CreateWaitFunc(Handle, hWnd, Msg, Timeout := -1)
{
    static __PageSize__, __Offset__, __Ptr__, __PageNum__
    global __Free__

    if((!__Offset__)||((__PageSize__-__Offset__<(A_PtrSize = 4 ? 49 : 85))&&(!__Free__.MaxIndex())))
    {
        if(!__Ptr__)
        {
            __Ptr__ := Array()
            __Free__ := Array()
        }
        __PageNum__++
        VarSetCapacity(FuncName, 0x30, 0)
        __Ptr__[__PageNum__] := DllCall("VirtualAlloc", "ptr", 0, "ptr", 1, "uint", 0x1000, "uint", 0x40, "ptr")
        DllCall("VirtualQuery", "ptr", 0|__Ptr__[__PageNum__], "ptr", &FuncName, "ptr", 0x30)
        __PageSize__ := NumGet(&FuncName, 3*A_PtrSize, "ptr")
        StrPut("WaitForSingleObject", &FuncName, 0x30, "CP1250")
        NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "kernel32.dll"), "ptr", &FuncName, "ptr"), 0|__Ptr__[__PageNum__], 0, "ptr")
        StrPut("PostMessageW", &FuncName, 0x30, "CP1250")
        NumPut(DllCall("GetProcAddress", "ptr", DllCall("GetModuleHandle", "str", "user32.dll"), "ptr", &FuncName, "ptr"), 0|__Ptr__[__PageNum__], A_PtrSize, "ptr")
        __Offset__ := 2*A_PtrSize
    }
    if(!__Free__.MaxIndex())
    {
        ptr := __Ptr__[__PageNum__]+__Offset__
        __Offset__+=(A_PtrSize = 4 ? 49 : 85)
    }
    else
        ptr := __Free__.Remove(1)
    if(A_PtrSize=4)
    {
        NumPut(0x68, 0|ptr, 0, "uchar")
        NumPut(Timeout, 0|ptr, 1, "uint")
        NumPut(0x68, 0|ptr, 5, "uchar")
        NumPut(Handle, 0|ptr, 6, "ptr")
        NumPut(0x15FF, 0|ptr, 10, "ushort")
        NumPut(0|__Ptr__[__PageNum__], 0|ptr, 12, "ptr")
        NumPut(0x50, 0|ptr, 16, "uchar")
        NumPut(0x68, 0|ptr, 17, "uchar")
        NumPut(Handle, 0|ptr, 18, "ptr")
        NumPut(0x68, 0|ptr, 22, "uchar")
        NumPut(Msg, 0|ptr, 23, "uint")
        NumPut(0x68, 0|ptr, 27, "uchar")
        NumPut(hWnd, 0|ptr, 28, "ptr")
        NumPut(0x15FF, 0|ptr, 32, "ushort")
        NumPut(0|__Ptr__[__PageNum__]+4, 0|ptr, 34, "ptr")
        NumPut(0xC2, 0|ptr, 38, "uchar")
        NumPut(4, 0|ptr, 39, "ushort")
    }
    else
    {
        NumPut(0x53, 0|ptr, 0, "uchar")
        NumPut(0x20EC8348, 0|ptr, 1, "uint")
        NumPut(0xBACB8948, 0|ptr, 5, "uint")
        NumPut(Timeout, 0|ptr, 9, "uint")
        NumPut(0xB948, 0|ptr, 13, "ushort")
        NumPut(Handle, 0|ptr, 15, "ptr")
        NumPut(0x15FF, 0|ptr, 23, "ushort")
        NumPut(-29-(ptr-__Ptr__[__PageNum__]), 0|ptr, 25, "uint")
        NumPut(0xB849, 0|ptr, 29, "ushort")
        NumPut(Handle, 0|ptr, 31, "ptr")
        NumPut(0xBA, 0|ptr, 39, "uchar")
        NumPut(Msg, 0|ptr, 40, "uint")
        NumPut(0xB948, 0|ptr, 44, "ushort")
        NumPut(hWnd, 0|ptr, 46, "ptr")
        NumPut(0xC18941, 0|ptr, 54, "uint")
        NumPut(0x15FF, 0|ptr, 57, "ushort")
        NumPut(-55-(ptr-__Ptr__[__PageNum__]), 0|ptr, 59, "uint")
        NumPut(0x20C48348, 0|ptr, 63, "uint")
        NumPut(0x5B, 0|ptr, 67, "uchar")
        NumPut(0xC3, 0|ptr, 68, "uchar")
    }
    return ptr
}

Использование: вызов OnDirectoryChanges с первым параметром — имя папки за которой нужно следить, вторым параметром — именем процедуры которая будет выполняться при каждом зафиксированном изменении.

Процедура, имя которой передаётся в качестве второго параметра должна обрабатывать два параметра: первый — полный путь к файлу который был изменён, второй — тип события.

В прочих аспектах OnDirectoryChanges подобна OnMessage.

10

Re: AHK: асинхронный вызов Wait-функции

У меня на изменение содержания и атрибутов файла на рабочем столе не среагировало.

Вообще подозреваю, что с помощью WMI всё это можно организовать куда проще.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

11 (изменено: creature.ws, 2012-11-08 16:32:34)

Re: AHK: асинхронный вызов Wait-функции

Третий параметр OnDirectoryChanges — ссылка на dwNotifyFilter, для дополнительного реагирования на изменение атрибутов и времени записи указать нужно 15.
По-умолчанию 3 — изменнения имени папки или файла.

Буду рад видеть пример использующий WMI.

12

Re: AHK: асинхронный вызов Wait-функции

Ну вот, а ты говорил — «Дружелюбный к пользователю»! Откуда пользователю должно быть известно про 15?

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

13

Re: AHK: асинхронный вызов Wait-функции

creature.ws пишет:

Буду рад увидеть пример использующий WMI.

Вечером посмотрю.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

14 (изменено: creature.ws, 2012-11-08 16:35:47)

Re: AHK: асинхронный вызов Wait-функции

Откуда пользователю должно быть известно про 15?

Предложи интуитивно понятный пользователю вариант задания фильтра событий, у меня идей нет

15

Re: AHK: асинхронный вызов Wait-функции

teadrinker пишет:

Вообще подозреваю, что с помощью WMI всё это можно организовать куда проще.

По-моему там только  __InstanceCreationEvent, который никак нельзя считать достойной альтернативой.

16

Re: AHK: асинхронный вызов Wait-функции

Да не, сейчас вот смотрю, там ещё __InstanceDeletionEvent и __InstanceModificationEvent.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

17

Re: AHK: асинхронный вызов Wait-функции

teadrinker пишет:

Да не, сейчас вот смотрю, там ещё __InstanceDeletionEvent и __InstanceModificationEvent.

Ну и один хрен ведь, просто проверка состояния через равные промежутки времени, разве нет?

18

Re: AHK: асинхронный вызов Wait-функции

Да, а чем это хуже? Ведь тоже можно выполнять асинхронно.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

19

Re: AHK: асинхронный вызов Wait-функции

teadrinker пишет:

Да, а чем это хуже? Ведь тоже можно выполнять асинхронно.

Ну начнём с того, что это совершенно разные механизмы- ReadDirectoryChangesW ждёт изменений, а WMI сравнивает состояние системы в разные моменты времени. Т.е. в первом случае мы гарантированно узнаем о событии, а во втором только если оно будет достаточно продолжительным. Если установить маленький промежуток времени между проверками, то можно сильно загрузить процессор. В этой теме рассмотрено слежение в реальном времени, для него WMI не подходит. Обычное слежение можно организовать и в синхронном режиме, просто задав буфер побольше и периодически проверяя события, так мы тоже ничего не упустим, а WMI опять пролетает. Можно и плюсы у WMI найти, например у его процесса может быть больше прав и он сможет работать с папками, которые по каким-то причинам не удалось открыть.

20

Re: AHK: асинхронный вызов Wait-функции

Александр_ пишет:

WMI сравнивает состояние системы в разные моменты времени

Да, согласен, это не Wait-функция. Но я только сейчас узнал, каков механизм работы WMI в данном случае.

Разработка AHK-скриптов:
e-mail dfiveg@mail.ru
Telegram jollycoder

21

Re: AHK: асинхронный вызов Wait-функции

priorProcName := EventList.Remove(eventHandle).UserDefinedProc.Name

eventHandle - число, Remove(eventHandle) может изменить остальные сохранённые хендлы.
Должно быть:

priorProcName := EventList.Remove(eventHandle, "").UserDefinedProc.Name