1 (изменено: Alectric, 2025-03-03 17:23:11)

Тема: 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.
Тема для обсуждения

Win 10 x64
AHK
                       Справка AHK v1 тебе в помощь.