Тема: AHK v2: Два потока
Делюсь методом запуска скрипта в два потока с "общей" памятью для обмена данными.
;#Requires AutoHotkey v2.0.19
#SingleInstance off ; здесь просто привычный набор параметров, ничего особенного
CoordMode "ToolTip"
SetControlDelay -1
SetKeyDelay -1
SetWinDelay -1
SetWorkingDir A_ScriptDir
DetectHiddenWindows true
#NoTrayIcon
onexit Exit
; создаем память которая будет общей для двух потоков
DataSize:=1000
Data:=Buffer(DataSize)
; псевдо-структура (смещения в общей памяти)
_MainCycleNum:=0 ; uint = 4 byte
_SlaveCycleNum:=4 ; uint = 4 byte
_MainString:=8, MainStringSize:=12 ; string
_SlaveString:=32 ,SlaveStringSize:=50 ; string
; принимаем параметры для второго потока
SecondThread:=DataPointer:=0
if (A_Args.Length=2)
{
SecondThread:=A_Args[1] ; PID первого потока.
DataPointer:=A_Args[2] ; указатель на общую память.
}
if !SecondThread ; код первого потока здесь
{
MainPID:=DllCall("GetCurrentProcessId") ; берем PID первого потока
; добавляем горячих клавиш по вкусу, через команду HotKey
HotKey "Esc",exit
; не советую добавлять их обычным методом, иначе второй поток будет тоже их обрабатывать
; запускаем второй поток и передаем ему параметры
if a_iscompiled
{
run a_scriptfullpath " " MainPID " " Data.Ptr,,,&SlavePID
}
else
{
run "`"" a_ahkPath "`" `"" a_scriptfullpath "`" " MainPID " " Data.Ptr,,,&SlavePID
}
; ---------
; запускаем контроль наличия второго потока, если его нет то завершаем скрипт
F:=CheckPIDExist.Bind(SlavePID)
settimer F,2000
MainLoop: ; просто метка для ориентации в коде
loop ; основной цикл первого потока (""""секция автовыполнения"""")
{
sleep 500
NumPut("uint",a_index,Data,_MainCycleNum) ; пишем номер цикла в общую память (данные для второго потока)
tmp0:=NumGet(Data,_SlaveCycleNum,"uint") ; читаем данные из общей памяти (данные от второго потока)
tmp1:=StrGet(Data.Ptr+_SlaveString,SlaveStringSize) ; читаем строку
StrPut("Hello World!",Data.Ptr+_MainString,MainStringSize) ; пишем строку
; выводим на экран
tooltip "Main Script`nMainPID=" MainPID "`nSlavePID=" SlavePID "`nA_Index = " a_index "`nData from slave:`n`t" tmp0 "`n`t" tmp1,0,500
}
}
else ; код второго потока здесь
{
; запускаем контроль наличия первого потока, если его нет то завершаем скрипт
MainPID:=SecondThread
F:=CheckPIDExist.Bind(MainPID)
settimer F,2000
SlavePID:=DllCall("GetCurrentProcessId")
A_IconHidden:=false
; получаем объект для работы с памятью первого потока
MainMemory := ClassMemory("Ahk_PID" MainPID)
SlaveLoop: ; просто метка для ориентации в коде
loop ; основной цикл второго потока (""""секция автовыполнения"""")
{
sleep 100
MainMemory.readRaw(DataPointer,Data,DataSize) ; читаем общую память в переменную "Data"
MainMemory.write(DataPointer+_SlaveCycleNum,A_Index) ; пишем номер цикла в общую память (данные для первого потока)
tmp0:=numget(Data,_MainCycleNum,"Uint") ; читаем данные из общей памяти (данные от первого потока)
tmp1:=StrGet(Data.Ptr+_MainString,MainStringSize) ; читаем строку
str:=buffer(SlaveStringSize) ; пишем строку
StrPut("Hello Yourself!",str.Ptr,SlaveStringSize) ; пишем строку
MainMemory.writeRaw(DataPointer+_SlaveString,str,SlaveStringSize) ; пишем строку
; выводим на экран
tooltip "Slave Script`nMainPID=" MainPID "`nSlavePID=" SlavePID "`nA_Index = " a_index "`nData from main:`n`t" tmp0 "`n`t" tmp1,200,500
}
}
CheckPIDExist(PID)
{
if !ProcessExist(PID)
Exit()
}
exit(*)
{
exitapp
}
; в этом месте можно добавлять метки перехода для интерфейса и т.д.
; библиотека
; Переделанный и урезанный basic memory class by RHCP:
; https://github.com/Kalamity/classMemory
class ClassMemory
{
; List of useful accessible values. Some of these inherited values (the non objects) are set when the new operator is used.
static aTypeSize := { UChar: 1, Char: 1
, UShort: 2, Short: 2
, UInt: 4, Int: 4
, UFloat: 4, Float: 4
, Int64: 8, Double: 8}
static aRights := { PROCESS_ALL_ACCESS: 0x001F0FFF
, PROCESS_CREATE_PROCESS: 0x0080
, PROCESS_CREATE_THREAD: 0x0002
, PROCESS_DUP_HANDLE: 0x0040
, PROCESS_QUERY_INFORMATION: 0x0400
, PROCESS_QUERY_LIMITED_INFORMATION: 0x1000
, PROCESS_SET_INFORMATION: 0x0200
, PROCESS_SET_QUOTA: 0x0100
, PROCESS_SUSPEND_RESUME: 0x0800
, PROCESS_TERMINATE: 0x0001
, PROCESS_VM_OPERATION: 0x0008
, PROCESS_VM_READ: 0x0010
, PROCESS_VM_WRITE: 0x0020
, SYNCHRONIZE: 0x00100000}
; Method: __new(program, dwDesiredAccess := "", byRef handle := "", windowMatchMode := 3)
; Example: derivedObject := new _ClassMemory("ahk_exe calc.exe")
; This is the first method which should be called when trying to access a program's memory.
; If the process is successfully opened, an object is returned which can be used to read that processes memory space.
; [derivedObject].hProcess stores the opened handle.
; If the target process closes and re-opens, simply free the derived object and use the new operator again to open a new handle.
; Parameters:
; program The program to be opened. This can be any AHK windowTitle identifier, such as
; ahk_exe, ahk_class, ahk_pid, or simply the window title. e.g. "ahk_exe calc.exe" or "Calculator".
; It's safer not to use the window title, as some things can have the same window title e.g. an open folder called "Starcraft II"
; would have the same window title as the game itself.
; *'DetectHiddenWindows, On' is required for hidden windows*
; dwDesiredAccess The access rights requested when opening the process.
; If this parameter is null the process will be opened with the following rights
; PROCESS_QUERY_INFORMATION, PROCESS_VM_OPERATION, PROCESS_VM_READ, PROCESS_VM_WRITE, & SYNCHRONIZE
; This access level is sufficient to allow all of the methods in this class to work.
; Specific process access rights are listed here http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
; handle (Output) Optional variable in which a copy of the opened processes handle will be stored.
; Values:
; Null OpenProcess failed. The script may need to be run with admin rights admin,
; and/or with the use of _ClassMemory.setSeDebugPrivilege(). Consult A_LastError for more information.
; 0 The program isn't running (not found) or you passed an incorrect program identifier parameter.
; In some cases _ClassMemory.setSeDebugPrivilege() may be required.
; Positive Integer A handle to the process. (Success)
; windowMatchMode - Determines the matching mode used when finding the program (windowTitle).
; The default value is 3 i.e. an exact match. Refer to AHK's setTitleMathMode for more information.
; Return Values:
; Object On success an object is returned which can be used to read the processes memory.
; Null Failure. A_LastError and the optional handle parameter can be consulted for more information.
__new(program, dwDesiredAccess := "", handle := "", windowMatchMode := 3)
{
if this.PID := handle := this.findPID(program, windowMatchMode) ; set handle to 0 if program not found
{
; This default access level is sufficient to read and write memory addresses, and to perform pattern scans.
; if the program is run using admin privileges, then this script will also need admin privileges
if !IsInteger(dwDesiredAccess)
dwDesiredAccess := ClassMemory.aRights.PROCESS_QUERY_INFORMATION | ClassMemory.aRights.PROCESS_VM_OPERATION | ClassMemory.aRights.PROCESS_VM_READ | ClassMemory.aRights.PROCESS_VM_WRITE
dwDesiredAccess |= ClassMemory.aRights.SYNCHRONIZE ; add SYNCHRONIZE to all handles to allow isHandleValid() to work
if this.hProcess := handle := this.OpenProcess(this.PID, dwDesiredAccess) ; NULL/Blank if failed to open process for some reason
{
this.pNumberOfBytesRead := DllCall("GlobalAlloc", "UInt", 0x0040, "Ptr", A_PtrSize, "Ptr") ; 0x0040 initialise to 0
this.pNumberOfBytesWritten := DllCall("GlobalAlloc", "UInt", 0x0040, "Ptr", A_PtrSize, "Ptr") ; initialise to 0
this.readStringLastError := False
this.currentProgram := program
if this.isTarget64bit := this.isTargetProcess64Bit(this.PID, this.hProcess, dwDesiredAccess)
this.ptrType := "Int64"
else this.ptrType := "UInt" ; If false or Null (fails) assume 32bit
; if script is 64 bit, getModuleBaseAddress() should always work
; if target app is truly 32 bit, then getModuleBaseAddress()
; will work when script is 32 bit
if (A_PtrSize != 4 || !this.isTarget64bit)
this.BaseAddress := this.getModuleBaseAddress()
; If the above failed or wasn't called, fall back to alternate method
if this.BaseAddress < 0 || !this.BaseAddress
this.BaseAddress := this.getProcessBaseAddress(program, windowMatchMode)
return this
}
}
return
}
__delete()
{
this.closeHandle(this.hProcess)
if this.pNumberOfBytesRead
DllCall("GlobalFree", "Ptr", this.pNumberOfBytesRead)
if this.pNumberOfBytesWritten
DllCall("GlobalFree", "Ptr", this.pNumberOfBytesWritten)
return
}
version()
{
return 9999.9999
}
findPID(program, windowMatchMode := "3")
{
; If user passes an AHK_PID, don't bother searching. There are cases where searching windows for PIDs
; wont work - console apps
if RegExMatch(program, "i)\s*AHK_PID\s+(0x[[:xdigit:]]+|\d+)", &pid)
return pid[1]
if windowMatchMode
{
; This is a string and will not contain the 0x prefix
mode := A_TitleMatchMode
; remove hex prefix as SetTitleMatchMode will throw a run time error. This will occur if integer mode is set to hex and user passed an int (unquoted)
windowMatchMode:=StrReplace(windowMatchMode,"0x")
; StringReplace, windowMatchMode, windowMatchMode, 0x
SetTitleMatchMode windowMatchMode
}
pid:=WinGetPid(program)
if windowMatchMode
SetTitleMatchMode mode ; In case executed in autoexec
; If use 'ahk_exe test.exe' and winget fails (which can happen when setSeDebugPrivilege is required),
; try using the process command. When it fails due to setSeDebugPrivilege, setSeDebugPrivilege will still be required to openProcess
; This should also work for apps without windows.
if (!pid && RegExMatch(program, "i)\bAHK_EXE\b\s*(.*)", &fileNm))
{
; remove any trailing AHK_XXX arguments
filename := RegExReplace(fileNm[1], "i)\bahk_(class|id|pid|group)\b.*", "")
filename := trim(filename) ; extra spaces will make process command fail
; AHK_EXE can be the full path, so just get filename
SplitPath fileName , &fileName
if (fileName) ; if filename blank, scripts own pid is returned
pid := ProcessExist(fileName)
}
return pid ? pid : 0 ; PID is null on fail, return 0
}
; Method: isHandleValid()
; This method provides a means to check if the internal process handle is still valid
; or in other words, the specific target application instance (which you have been reading from)
; has closed or restarted.
; For example, if the target application closes or restarts the handle will become invalid
; and subsequent calls to this method will return false.
;
; Return Values:
; True The handle is valid.
; False The handle is not valid.
;
; Notes:
; This operation requires a handle with SYNCHRONIZE access rights.
; All handles, even user specified ones are opened with the SYNCHRONIZE access right.
isHandleValid()
{
return 0x102 = DllCall("WaitForSingleObject", "Ptr", this.hProcess, "UInt", 0)
; WaitForSingleObject return values
; -1 if called with null hProcess (sets lastError to 6 - invalid handle)
; 258 / 0x102 WAIT_TIMEOUT - if handle is valid (process still running)
; 0 WAIT_OBJECT_0 - if process has terminated
}
; Method: openProcess(PID, dwDesiredAccess)
; ***Note: This is an internal method which shouldn't be called directly unless you absolutely know what you are doing.
; This is because the new operator, in addition to calling this method also sets other values
; which are required for the other methods to work correctly.
; Parameters:
; PID The Process ID of the target process.
; dwDesiredAccess The access rights requested when opening the process.
; Specific process access rights are listed here http://msdn.microsoft.com/en-us/library/windows/desktop/ms684880(v=vs.85).aspx
; Return Values:
; Null/blank OpenProcess failed. If the target process has admin rights, then the script also needs to be ran as admin.
; _ClassMemory.setSeDebugPrivilege() may also be required.
; Positive integer A handle to the process.
openProcess(PID, dwDesiredAccess)
{
r := DllCall("OpenProcess", "UInt", dwDesiredAccess, "Int", False, "UInt", PID, "Ptr")
; if it fails with 0x5 ERROR_ACCESS_DENIED, try enabling privilege ... lots of users never try this.
; there may be other errors which also require DebugPrivilege....
if (!r && A_LastError = 5)
{
this.setSeDebugPrivilege(true) ; no harm in enabling it if it is already enabled by user
if (r2 := DllCall("OpenProcess", "UInt", dwDesiredAccess, "Int", False, "UInt", PID, "Ptr"))
return r2
DllCall("SetLastError", "UInt", 5) ; restore original error if it doesnt work
}
; If fails with 0x5 ERROR_ACCESS_DENIED (when setSeDebugPrivilege() is req.), the func. returns 0 rather than null!! Set it to null.
; If fails for another reason, then it is null.
return r ? r : 0
}
; Method: closeHandle(hProcess)
; Note: This is an internal method which is automatically called when the script exits or the derived object is freed/destroyed.
; There is no need to call this method directly. If you wish to close the handle simply free the derived object.
; i.e. derivedObject := [] ; or derivedObject := ""
; Parameters:
; hProcess The handle to the process, as returned by openProcess().
; Return Values:
; Non-Zero Success
; 0 Failure
closeHandle(hProcess)
{
return DllCall("CloseHandle", "Ptr", hProcess)
}
; Methods: numberOfBytesRead() / numberOfBytesWritten()
; Returns the number of bytes read or written by the last ReadProcessMemory or WriteProcessMemory operation.
;
; Return Values:
; zero or positive value Number of bytes read/written
; -1 Failure. Shouldn't occur
numberOfBytesRead()
{
return !this.pNumberOfBytesRead ? -1 : NumGet(this.pNumberOfBytesRead+0, "Ptr")
}
numberOfBytesWritten()
{
return !this.pNumberOfBytesWritten ? -1 : NumGet(this.pNumberOfBytesWritten+0, "Ptr")
}
; Method: read(address, type := "UInt", aOffsets*)
; Reads various integer type values
; Parameters:
; address - The memory address of the value or if using the offset parameter,
; the base address of the pointer.
; type - The integer type.
; Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
; Note: Types must not contain spaces i.e. " UInt" or "UInt " will not work.
; When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
; aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
; The address (base address) and offsets should point to the memory address which holds the integer.
; Return Values:
; integer - Indicates success.
; Null - Indicates failure. Check ErrorLevel and A_LastError for more information.
; Note: Since the returned integer value may be 0, to check for success/failure compare the result
; against null i.e. if (result = "") then an error has occurred.
; When reading doubles, adjusting "SetFormat, float, totalWidth.DecimalPlaces"
; may be required depending on your requirements.
read(address, type := "UInt")
{
; If invalid type RPM() returns success (as bytes to read resolves to null in dllCall())
; so set errorlevel to invalid parameter for DLLCall() i.e. -2
if !ClassMemory.aTypeSize.%type%
return false
result:=0
if DllCall("ReadProcessMemory", "Ptr", this.hProcess, "Ptr", address, type "*", &result, "Ptr", ClassMemory.aTypeSize.%type%, "Ptr", this.pNumberOfBytesRead)
return result
return
}
; Method: readRaw(address, byRef buffer, bytes := 4, aOffsets*)
; Reads an area of the processes memory and stores it in the buffer variable
; Parameters:
; address - The memory address of the area to read or if using the offsets parameter
; the base address of the pointer which points to the memory region.
; buffer - The unquoted variable name for the buffer. This variable will receive the contents from the address space.
; This method calls varsetCapcity() to ensure the variable has an adequate size to perform the operation.
; If the variable already has a larger capacity (from a previous call to varsetcapcity()), then it will not be shrunk.
; Therefore it is the callers responsibility to ensure that any subsequent actions performed on the buffer variable
; do not exceed the bytes which have been read - as these remaining bytes could contain anything.
; bytes - The number of bytes to be read.
; aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
; The address (base address) and offsets should point to the memory address which is to be read
; Return Values:
; Non Zero - Indicates success.
; Zero - Indicates failure. Check errorLevel and A_LastError for more information
;
; Notes: The contents of the buffer may then be retrieved using AHK's NumGet() and StrGet() functions.
; This method offers significant (~30% and up) performance boost when reading large areas of memory.
; As calling ReadProcessMemory for four bytes takes a similar amount of time as it does for 1,000 bytes.
readRaw(address, buffer, bytes := 4)
{
; Data.__new(bytes)
return DllCall("ReadProcessMemory", "Ptr", this.hProcess, "Ptr", address, "Ptr", IsInteger(Buffer)?Buffer:buffer.ptr, "Ptr", bytes, "Ptr", this.pNumberOfBytesRead)
}
; Method: write(address, value, type := "Uint", aOffsets*)
; Writes various integer type values to the process.
; Parameters:
; address - The memory address to which data will be written or if using the offset parameter,
; the base address of the pointer.
; type - The integer type.
; Valid types are UChar, Char, UShort, Short, UInt, Int, Float, Int64 and Double.
; Note: Types must not contain spaces i.e. " UInt" or "UInt " will not work.
; When an invalid type is passed the method returns NULL and sets ErrorLevel to -2
; aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
; The address (base address) and offsets should point to the memory address which is to be written to.
; Return Values:
; Non Zero - Indicates success.
; Zero - Indicates failure. Check errorLevel and A_LastError for more information
; Null - An invalid type was passed. Errorlevel is set to -2
write(address, value, type := "Uint")
{
if !ClassMemory.aTypeSize.%type%
return false
return DllCall("WriteProcessMemory", "Ptr", this.hProcess, "Ptr", address, type "*", &value, "Ptr", ClassMemory.aTypeSize.%type%, "Ptr", this.pNumberOfBytesWritten)
}
; Method: writeRaw(address, pBuffer, sizeBytes, aOffsets*)
; Writes a buffer to the process.
; Parameters:
; address - The memory address to which the contents of the buffer will be written
; or if using the offset parameter, the base address of the pointer.
; pBuffer - A pointer to the buffer which is to be written.
; This does not necessarily have to be the beginning of the buffer itself e.g. pBuffer := &buffer + offset
; sizeBytes - The number of bytes which are to be written from the buffer.
; aOffsets* - A variadic list of offsets. When using offsets the address parameter should equal the base address of the pointer.
; The address (base address) and offsets should point to the memory address which is to be written to.
; Return Values:
; Non Zero - Indicates success.
; Zero - Indicates failure. Check errorLevel and A_LastError for more information
writeRaw(address, Buffer, sizeBytes)
{
return DllCall("WriteProcessMemory", "Ptr", this.hProcess, "Ptr", address, "Ptr", IsInteger(Buffer)?Buffer:Buffer.Ptr, "Ptr", sizeBytes, "Ptr", this.pNumberOfBytesWritten)
}
; Interesting note:
; Although handles are 64-bit pointers, only the less significant 32 bits are employed in them for the purpose
; of better compatibility (for example, to enable 32-bit and 64-bit processes interact with each other)
; Here are examples of such types: HANDLE, HWND, HMENU, HPALETTE, HBITMAP, etc.
; http://www.viva64.com/en/k/0005/
; Method: getProcessBaseAddress(WindowTitle, windowMatchMode := 3)
; Returns the base address of a process. In most cases this will provide the same result as calling getModuleBaseAddress() (when passing
; a null value as the module parameter), however getProcessBaseAddress() will usually work regardless of the bitness
; of both the AHK exe and the target process.
; *This method relies on the target process having a window and will not work for console apps*
; *'DetectHiddenWindows, On' is required for hidden windows*
; ***If this returns an incorrect value, try using (the MORE RELIABLE) getModuleBaseAddress() instead.***
; Parameters:
; windowTitle This can be any AHK windowTitle identifier, such as
; ahk_exe, ahk_class, ahk_pid, or simply the window title. e.g. "ahk_exe calc.exe" or "Calculator".
; It's safer not to use the window title, as some things can have the same window title e.g. an open folder called "Starcraft II"
; would have the same window title as the game itself.
; windowMatchMode Determines the matching mode used when finding the program's window (windowTitle).
; The default value is 3 i.e. an exact match. The current matchmode will be used if the parameter is null or 0.
; Refer to AHK's setTitleMathMode for more information.
; Return Values:
; Positive integer The base address of the process (success).
; Null The process's window couldn't be found.
; 0 The GetWindowLong or GetWindowLongPtr call failed. Try getModuleBaseAddress() instead.
getProcessBaseAddress(windowTitle, windowMatchMode := "3")
{
mode:=0
if (windowMatchMode && A_TitleMatchMode != windowMatchMode)
{
mode := A_TitleMatchMode ; This is a string and will not contain the 0x prefix
windowMatchMode:=StrReplace(windowMatchMode,"0x")
; StringReplace windowMatchMode, windowMatchMode, 0x ; remove hex prefix as SetTitleMatchMode will throw a run time error. This will occur if integer mode is set to hex and matchmode param is passed as an number not a string.
SetTitleMatchMode windowMatchMode ;mode 3 is an exact match
}
hWnd:=WinGetID(WindowTitle)
if mode
SetTitleMatchMode mode ; In case executed in autoexec
if !hWnd
return 0 ; return blank failed to find window
; GetWindowLong returns a Long (Int) and GetWindowLongPtr return a Long_Ptr
return DllCall(A_PtrSize = 4 ; If DLL call fails, returned value will = 0
? "GetWindowLong"
: "GetWindowLongPtr"
, "Ptr", hWnd, "Int", -6, A_Is64bitOS ? "Int64" : "UInt")
; For the returned value when the OS is 64 bit use Int64 to prevent negative overflow when AHK is 32 bit and target process is 64bit
; however if the OS is 32 bit, must use UInt, otherwise the number will be huge (however it will still work as the lower 4 bytes are correct)
; Note - it's the OS bitness which matters here, not the scripts/AHKs
}
; http://winprogger.com/getmodulefilenameex-enumprocessmodulesex-failures-in-wow64/
; http://stackoverflow.com/questions/3801517/how-to-enum-modules-in-a-64bit-process-from-a-32bit-wow-process
; Method: getModuleBaseAddress(module := "", byRef aModuleInfo := "")
; Parameters:
; moduleName - The file name of the module/dll to find e.g. "calc.exe", "GDI32.dll", "Bass.dll" etc
; If no module (null) is specified, the address of the base module - main()/process will be returned
; e.g. for calc.exe the following two method calls are equivalent getModuleBaseAddress() and getModuleBaseAddress("calc.exe")
; aModuleInfo - (Optional) A module Info object is returned in this variable. If method fails this variable is made blank.
; This object contains the keys: name, fileName, lpBaseOfDll, SizeOfImage, and EntryPoint
; Return Values:
; Positive integer - The module's base/load address (success).
; -1 - Module not found
; -3 - EnumProcessModulesEx failed
; -4 - The AHK script is 32 bit and you are trying to access the modules of a 64 bit target process. Or the target process has been closed.
; Notes: A 64 bit AHK can enumerate the modules of a target 64 or 32 bit process.
; A 32 bit AHK can only enumerate the modules of a 32 bit process
; This method requires PROCESS_QUERY_INFORMATION + PROCESS_VM_READ access rights. These are included by default with this class.
getModuleBaseAddress()
{
moduleName := this.GetModuleFileNameEx(0, True) ; main executable module of the process - get just fileName no path
aModules:=map()
if (r := this.getModules(&aModules, True) < 0)
return r ; -4, -3
return aModules.has(moduleName) ? (aModules[moduleName].lpBaseOfDll) : -1
; no longer returns -5 for failed to get module info
}
; SeDebugPrivileges is required to read/write memory in some programs.
; This only needs to be called once when the script starts,
; regardless of the number of programs being read (or if the target programs restart)
; Call this before attempting to call any other methods in this class
; i.e. call _ClassMemory.setSeDebugPrivilege() at the very start of the script.
setSeDebugPrivilege(enable := True)
{
h := DllCall("OpenProcess", "UInt", 0x0400, "Int", false, "UInt", DllCall("GetCurrentProcessId"), "Ptr")
; Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32)
t:=0
DllCall("Advapi32.dll\OpenProcessToken", "Ptr", h, "UInt", 32, "PtrP", &t)
ti:=Buffer(16,0)
; VarSetCapacity(ti, 16, 0) ; structure of privileges
NumPut("UInt",1, ti, 0) ; one entry in the privileges array...
; Retrieves the locally unique identifier of the debug privilege:
luid:=0
DllCall("Advapi32.dll\LookupPrivilegeValue", "Ptr", 0, "Str", "SeDebugPrivilege", "Int64P", &luid)
NumPut("Int64",luid, ti, 4)
if enable
NumPut("UInt",2, ti, 12) ; enable this privilege: SE_PRIVILEGE_ENABLED = 2
; Update the privileges of this process with the new access token:
r := DllCall("Advapi32.dll\AdjustTokenPrivileges", "Ptr", t, "Int", false, "Ptr", ti.Ptr, "UInt", 0, "Ptr", 0, "Ptr", 0)
DllCall("CloseHandle", "Ptr", t) ; close this access token handle to save memory
DllCall("CloseHandle", "Ptr", h) ; close this process handle to save memory
return r
}
; Method: isTargetProcess64Bit(PID, hProcess := "", currentHandleAccess := "")
; Determines if a process is 64 bit.
; Parameters:
; PID The Process ID of the target process. If required this is used to open a temporary process handle.
; hProcess (Optional) A handle to the process, as returned by openProcess() i.e. [derivedObject].hProcess
; currentHandleAccess (Optional) The dwDesiredAccess value used when opening the process handle which has been
; passed as the hProcess parameter. If specifying hProcess, you should also specify this value.
; Return Values:
; True The target application is 64 bit.
; False The target application is 32 bit.
; Null The method failed.
; Notes:
; This is an internal method which is called when the new operator is used. It is used to set the pointer type for 32/64 bit applications so the pointer methods will work.
; This operation requires a handle with PROCESS_QUERY_INFORMATION or PROCESS_QUERY_LIMITED_INFORMATION access rights.
; If the currentHandleAccess parameter does not contain these rights (or not passed) or if the hProcess (process handle) is invalid (or not passed)
; a temporary handle is opened to perform this operation. Otherwise if hProcess and currentHandleAccess appear valid
; the passed hProcess is used to perform the operation.
isTargetProcess64Bit(PID, hProcess := 0, currentHandleAccess := 0)
{
closeHandle:=0
if !A_Is64bitOS
return False
; If insufficient rights, open a temporary handle
else if !hProcess || !(currentHandleAccess & (ClassMemory.aRights.PROCESS_QUERY_INFORMATION | ClassMemory.aRights.PROCESS_QUERY_LIMITED_INFORMATION))
closeHandle := hProcess := this.openProcess(PID, ClassMemory.aRights.PROCESS_QUERY_INFORMATION)
Wow64Process:=0
if (hProcess && DllCall("IsWow64Process", "Ptr", hProcess, "Int*", &Wow64Process))
result := !Wow64Process
if closeHandle
this.CloseHandle(hProcess)
return result
}
/*
_Out_ PBOOL Wow64Proces value set to:
True if the process is running under WOW64 - 32bit app on 64bit OS.
False if the process is running under 32-bit Windows!
False if the process is a 64-bit application running under 64-bit Windows.
*/
getModules(&aModules, useFileNameAsKey := False)
{
if (A_PtrSize = 4 && this.IsTarget64bit)
return -4 ; AHK is 32bit and target process is 64 bit, this function wont work
; aModules := []
lphModule:=Buffer(4)
if !moduleCount := this.EnumProcessModulesEx(&lphModule)
return -3
loop moduleCount
{
aModuleInfo:=map()
hModule := numget(lphModule, (A_index - 1) * A_PtrSize,"Ptr")
this.GetModuleInformation(hModule, &aModuleInfo)
aModuleInfo.Name := this.GetModuleFileNameEx(hModule)
filePath := aModuleInfo.name
SplitPath filePath, &fileName
aModuleInfo.fileName := fileName
if useFileNameAsKey
aModules[fileName] := aModuleInfo
else aModules.insert(aModuleInfo)
}
return moduleCount
}
; lpFilename [out]
; A pointer to a buffer that receives the fully qualified path to the module.
; If the size of the file name is larger than the value of the nSize parameter, the function succeeds
; but the file name is truncated and null-terminated.
; If the buffer is adequate the string is still null terminated.
GetModuleFileNameEx(hModule := 0, fileNameNoPath := False)
{
; ANSI MAX_PATH = 260 (includes null) - unicode can be ~32K.... but no one would ever have one that size
; So just give it a massive size and don't bother checking. Most coders just give it MAX_PATH size anyway
VarSetStrCapacity(&lpFilename,1024)
; VarSetCapacity(lpFilename, 2048 * (A_IsUnicode ? 2 : 1))
DllCall("psapi\GetModuleFileNameEx"
, "Ptr", this.hProcess
, "Ptr", hModule
, "Str", lpFilename
, "Uint", 1024)
if fileNameNoPath
SplitPath lpFilename, &lpFilename ; strips the path so = GDI32.dll
return lpFilename
}
; dwFilterFlag
; LIST_MODULES_DEFAULT 0x0
; LIST_MODULES_32BIT 0x01
; LIST_MODULES_64BIT 0x02
; LIST_MODULES_ALL 0x03
; If the function is called by a 32-bit application running under WOW64, the dwFilterFlag option
; is ignored and the function provides the same results as the EnumProcessModules function.
EnumProcessModulesEx(&lphModule, dwFilterFlag := 0x03)
{
; lastError := A_LastError
size := 4 ; VarSetCapacity(lphModule, 4)
reqSize:=0
loop
{
if !DllCall("psapi\EnumProcessModulesEx"
, "Ptr", this.hProcess
, "Ptr", lphModule.Ptr
, "Uint", size
, "Uint*", &reqSize
, "Uint", dwFilterFlag)
return 0
else if (size >= reqSize)
break
else
{
size := reqSize
lphModule.__new(size)
}
}
; On first loop it fails with A_lastError = 0x299 as its meant to
; might as well reset it to its previous version
; DllCall("SetLastError", "UInt", lastError)
return reqSize // A_PtrSize ; module count ; sizeof(HMODULE) - enumerate the array of HMODULEs
}
GetModuleInformation(hModule,&aModuleInfo)
{
MODULEINFO:=buffer(A_PtrSize * 3)
; VarSetCapacity(MODULEINFO, A_PtrSize * 3)
; aModuleInfo := []
r:=DllCall("psapi\GetModuleInformation"
, "Ptr", this.hProcess
, "Ptr", hModule
, "Ptr", MODULEINFO.Ptr
, "UInt", A_PtrSize * 3)
aModuleInfo := { lpBaseOfDll: numget(MODULEINFO, 0, "Ptr")
, SizeOfImage: numget(MODULEINFO, A_PtrSize, "UInt")
, EntryPoint: numget(MODULEINFO, A_PtrSize * 2, "Ptr") }
return r
}
}
Предупреждение!
Есть вероятность недочетов при переводе с AHK v1. Версия для AHK v1.
Тема для обсуждения