Тема: 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.
Понимаю, что тема не раскрыта, но думаю пока хватит(устал писАть) . Замечания и дополнения приветствуются.