<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
	<title type="html"><![CDATA[Серый форум &mdash; AHK v2: Два потока]]></title>
	<link rel="self" href="https://forum.script-coding.com/extern.php?action=feed&amp;tid=18367&amp;type=atom" />
	<updated>2025-02-12T17:11:35Z</updated>
	<generator>PunBB</generator>
	<id>https://forum.script-coding.com/viewtopic.php?id=18367</id>
		<entry>
			<title type="html"><![CDATA[AHK v2: Два потока]]></title>
			<link rel="alternate" href="https://forum.script-coding.com/viewtopic.php?pid=161978#p161978" />
			<content type="html"><![CDATA[<p>Делюсь методом запуска скрипта в два потока с &quot;общей&quot; памятью для обмена данными.</p><div class="codebox"><pre><code>
;#Requires AutoHotkey v2.0.19

#SingleInstance off  ; здесь просто привычный набор параметров, ничего особенного
CoordMode &quot;ToolTip&quot;
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(&quot;GetCurrentProcessId&quot;) ; берем PID первого потока

; добавляем горячих клавиш по вкусу, через команду HotKey
  HotKey &quot;Esc&quot;,exit
; не советую добавлять их обычным методом, иначе второй поток будет тоже их обрабатывать

; запускаем второй поток и передаем ему параметры
  if a_iscompiled
  {
    run a_scriptfullpath &quot; &quot; MainPID &quot; &quot; Data.Ptr,,,&amp;SlavePID
  }
  else
  {
    run &quot;`&quot;&quot; a_ahkPath &quot;`&quot; `&quot;&quot; a_scriptfullpath &quot;`&quot; &quot; MainPID &quot; &quot; Data.Ptr,,,&amp;SlavePID
  }
  ; ---------

; запускаем контроль наличия второго потока, если его нет то завершаем скрипт
  F:=CheckPIDExist.Bind(SlavePID)
  settimer F,2000

MainLoop: ; просто метка для ориентации в коде
  loop ; основной цикл первого потока (&quot;&quot;&quot;&quot;секция автовыполнения&quot;&quot;&quot;&quot;)
  {
    sleep 500
    NumPut(&quot;uint&quot;,a_index,Data,_MainCycleNum) ; пишем номер цикла в общую память (данные для второго потока)
    tmp0:=NumGet(Data,_SlaveCycleNum,&quot;uint&quot;) ; читаем данные из общей памяти (данные от второго потока)

    tmp1:=StrGet(Data.Ptr+_SlaveString,SlaveStringSize) ; читаем строку
    StrPut(&quot;Hello World!&quot;,Data.Ptr+_MainString,MainStringSize) ; пишем строку
; выводим на экран
    tooltip &quot;Main Script`nMainPID=&quot; MainPID &quot;`nSlavePID=&quot; SlavePID &quot;`nA_Index = &quot; a_index &quot;`nData from slave:`n`t&quot; tmp0 &quot;`n`t&quot; tmp1,0,500
  }
}
else ; код второго потока здесь
{
; запускаем контроль наличия первого потока, если его нет то завершаем скрипт
  MainPID:=SecondThread
  F:=CheckPIDExist.Bind(MainPID)
  settimer F,2000

  SlavePID:=DllCall(&quot;GetCurrentProcessId&quot;)
  A_IconHidden:=false

; получаем объект для работы с памятью первого потока
  MainMemory := ClassMemory(&quot;Ahk_PID&quot; MainPID)

SlaveLoop: ; просто метка для ориентации в коде
  loop ; основной цикл второго потока (&quot;&quot;&quot;&quot;секция автовыполнения&quot;&quot;&quot;&quot;)
  {
    sleep 100
    MainMemory.readRaw(DataPointer,Data,DataSize) ; читаем общую память в переменную &quot;Data&quot;
    MainMemory.write(DataPointer+_SlaveCycleNum,A_Index) ; пишем номер цикла в общую память (данные для первого потока)
    tmp0:=numget(Data,_MainCycleNum,&quot;Uint&quot;) ; читаем данные из общей памяти (данные от первого потока)

    tmp1:=StrGet(Data.Ptr+_MainString,MainStringSize) ; читаем строку

    str:=buffer(SlaveStringSize) ; пишем строку
    StrPut(&quot;Hello Yourself!&quot;,str.Ptr,SlaveStringSize) ; пишем строку
    MainMemory.writeRaw(DataPointer+_SlaveString,str,SlaveStringSize) ; пишем строку

; выводим на экран
    tooltip &quot;Slave Script`nMainPID=&quot; MainPID &quot;`nSlavePID=&quot; SlavePID &quot;`nA_Index = &quot; a_index &quot;`nData from main:`n`t&quot; tmp0 &quot;`n`t&quot; 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 := &quot;&quot;, byRef handle := &quot;&quot;, windowMatchMode := 3)
    ; Example:  derivedObject := new _ClassMemory(&quot;ahk_exe calc.exe&quot;)
    ;           This is the first method which should be called when trying to access a program&#039;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. &quot;ahk_exe calc.exe&quot; or &quot;Calculator&quot;.
    ;                       It&#039;s safer not to use the window title, as some things can have the same window title e.g. an open folder called &quot;Starcraft II&quot;
    ;                       would have the same window title as the game itself.
    ;                       *&#039;DetectHiddenWindows, On&#039; 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, &amp; 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&#039;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&#039;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 := &quot;&quot;, handle := &quot;&quot;, windowMatchMode := 3)
    {
        DHW:=A_DetectHiddenWindows
        DetectHiddenWindows true
        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(&quot;GlobalAlloc&quot;, &quot;UInt&quot;, 0x0040, &quot;Ptr&quot;, A_PtrSize, &quot;Ptr&quot;) ; 0x0040 initialise to 0
                this.pNumberOfBytesWritten := DllCall(&quot;GlobalAlloc&quot;, &quot;UInt&quot;, 0x0040, &quot;Ptr&quot;, A_PtrSize, &quot;Ptr&quot;) ; initialise to 0

                this.readStringLastError := False
                this.currentProgram := program
                if this.isTarget64bit := this.isTargetProcess64Bit(this.PID, this.hProcess, dwDesiredAccess)
                    this.ptrType := &quot;Int64&quot;
                else this.ptrType := &quot;UInt&quot; ; 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&#039;t called, fall back to alternate method
                if this.BaseAddress &lt; 0 || !this.BaseAddress
                    this.BaseAddress := this.getProcessBaseAddress(program, windowMatchMode)
                DetectHiddenWindows DHW
                return this
            }
        }
        DetectHiddenWindows DHW
        return
    }

    __delete()
    {
        this.closeHandle(this.hProcess)
        if this.pNumberOfBytesRead
            DllCall(&quot;GlobalFree&quot;, &quot;Ptr&quot;, this.pNumberOfBytesRead)
        if this.pNumberOfBytesWritten
            DllCall(&quot;GlobalFree&quot;, &quot;Ptr&quot;, this.pNumberOfBytesWritten)
        return
    }

    version()
    {
        return 9999.9999
    }

    findPID(program, windowMatchMode := &quot;3&quot;)
    {
        ; If user passes an AHK_PID, don&#039;t bother searching. There are cases where searching windows for PIDs
        ; wont work - console apps
        if RegExMatch(program, &quot;i)\s*AHK_PID\s+(0x[[:xdigit:]]+|\d+)&quot;, &amp;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,&quot;0x&quot;)
;            StringReplace, windowMatchMode, windowMatchMode, 0x
            SetTitleMatchMode windowMatchMode
        }
        pid:=WinGetPid(program)
        if windowMatchMode
            SetTitleMatchMode mode    ; In case executed in autoexec

        ; If use &#039;ahk_exe test.exe&#039; 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 &amp;&amp; RegExMatch(program, &quot;i)\bAHK_EXE\b\s*(.*)&quot;, &amp;fileNm))
        {
            ; remove any trailing AHK_XXX arguments
            filename := RegExReplace(fileNm[1], &quot;i)\bahk_(class|id|pid|group)\b.*&quot;, &quot;&quot;)
            filename := trim(filename)    ; extra spaces will make process command fail
            ; AHK_EXE can be the full path, so just get filename
            SplitPath fileName , &amp;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(&quot;WaitForSingleObject&quot;, &quot;Ptr&quot;, this.hProcess, &quot;UInt&quot;, 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&#039;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(&quot;OpenProcess&quot;, &quot;UInt&quot;, dwDesiredAccess, &quot;Int&quot;, False, &quot;UInt&quot;, PID, &quot;Ptr&quot;)
        ; 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 &amp;&amp; A_LastError = 5)
        {
            this.setSeDebugPrivilege(true) ; no harm in enabling it if it is already enabled by user
            if (r2 := DllCall(&quot;OpenProcess&quot;, &quot;UInt&quot;, dwDesiredAccess, &quot;Int&quot;, False, &quot;UInt&quot;, PID, &quot;Ptr&quot;))
                return r2
            DllCall(&quot;SetLastError&quot;, &quot;UInt&quot;, 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 := &quot;&quot;
    ; Parameters:
    ;   hProcess        The handle to the process, as returned by openProcess().
    ; Return Values:
    ;   Non-Zero        Success
    ;   0               Failure

    closeHandle(hProcess)
    {
        return DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, 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&#039;t occur

    numberOfBytesRead()
    {
        return !this.pNumberOfBytesRead ? -1 : NumGet(this.pNumberOfBytesRead+0, &quot;Ptr&quot;)
    }
    numberOfBytesWritten()
    {
        return !this.pNumberOfBytesWritten ? -1 : NumGet(this.pNumberOfBytesWritten+0, &quot;Ptr&quot;)
    }


    ; Method:   read(address, type := &quot;UInt&quot;, 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. &quot; UInt&quot; or &quot;UInt &quot; 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 = &quot;&quot;) then an error has occurred.
    ;                   When reading doubles, adjusting &quot;SetFormat, float, totalWidth.DecimalPlaces&quot;
    ;                   may be required depending on your requirements.

    read(address, type := &quot;UInt&quot;)
    {
        ; 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(&quot;ReadProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, type &quot;*&quot;, &amp;result, &quot;Ptr&quot;, ClassMemory.aTypeSize.%type%, &quot;Ptr&quot;, 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&#039;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(&quot;ReadProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, &quot;Ptr&quot;, IsInteger(Buffer)?Buffer:buffer.ptr, &quot;Ptr&quot;, bytes, &quot;Ptr&quot;, this.pNumberOfBytesRead)
    }

    ; Method:   write(address, value, type := &quot;Uint&quot;, 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. &quot; UInt&quot; or &quot;UInt &quot; 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 := &quot;Uint&quot;)
    {
        if !ClassMemory.aTypeSize.%type%
            return false
        return DllCall(&quot;WriteProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, type &quot;*&quot;, &amp;value, &quot;Ptr&quot;, ClassMemory.aTypeSize.%type%, &quot;Ptr&quot;, 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 := &amp;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(&quot;WriteProcessMemory&quot;, &quot;Ptr&quot;, this.hProcess, &quot;Ptr&quot;, address, &quot;Ptr&quot;, IsInteger(Buffer)?Buffer:Buffer.Ptr, &quot;Ptr&quot;, sizeBytes, &quot;Ptr&quot;, 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*
    ;           *&#039;DetectHiddenWindows, On&#039; 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. &quot;ahk_exe calc.exe&quot; or &quot;Calculator&quot;.
    ;                       It&#039;s safer not to use the window title, as some things can have the same window title e.g. an open folder called &quot;Starcraft II&quot;
    ;                       would have the same window title as the game itself.
    ;   windowMatchMode     Determines the matching mode used when finding the program&#039;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&#039;s setTitleMathMode for more information.
    ; Return Values:
    ;   Positive integer    The base address of the process (success).
    ;   Null                The process&#039;s window couldn&#039;t be found.
    ;   0                   The GetWindowLong or GetWindowLongPtr call failed. Try getModuleBaseAddress() instead.


    getProcessBaseAddress(windowTitle, windowMatchMode := &quot;3&quot;)
    {
        mode:=0
        if (windowMatchMode &amp;&amp; A_TitleMatchMode != windowMatchMode)
        {
            mode := A_TitleMatchMode ; This is a string and will not contain the 0x prefix
            windowMatchMode:=StrReplace(windowMatchMode,&quot;0x&quot;)
;            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
            ? &quot;GetWindowLong&quot;
            : &quot;GetWindowLongPtr&quot;
            , &quot;Ptr&quot;, hWnd, &quot;Int&quot;, -6, A_Is64bitOS ? &quot;Int64&quot; : &quot;UInt&quot;)
            ; 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&#039;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 := &quot;&quot;, byRef aModuleInfo := &quot;&quot;)
    ; Parameters:
    ;   moduleName -    The file name of the module/dll to find e.g. &quot;calc.exe&quot;, &quot;GDI32.dll&quot;, &quot;Bass.dll&quot; 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(&quot;calc.exe&quot;)
    ;   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&#039;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(&amp;aModules, True) &lt; 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(&quot;OpenProcess&quot;, &quot;UInt&quot;, 0x0400, &quot;Int&quot;, false, &quot;UInt&quot;, DllCall(&quot;GetCurrentProcessId&quot;), &quot;Ptr&quot;)
        ; Open an adjustable access token with this process (TOKEN_ADJUST_PRIVILEGES = 32)
        t:=0
        DllCall(&quot;Advapi32.dll\OpenProcessToken&quot;, &quot;Ptr&quot;, h, &quot;UInt&quot;, 32, &quot;PtrP&quot;, &amp;t)
        ti:=Buffer(16,0)
;        VarSetCapacity(ti, 16, 0)  ; structure of privileges
        NumPut(&quot;UInt&quot;,1, ti, 0)  ; one entry in the privileges array...
        ; Retrieves the locally unique identifier of the debug privilege:
        luid:=0
        DllCall(&quot;Advapi32.dll\LookupPrivilegeValue&quot;, &quot;Ptr&quot;, 0, &quot;Str&quot;, &quot;SeDebugPrivilege&quot;, &quot;Int64P&quot;, &amp;luid)
        NumPut(&quot;Int64&quot;,luid, ti, 4)
        if enable
            NumPut(&quot;UInt&quot;,2, ti, 12)  ; enable this privilege: SE_PRIVILEGE_ENABLED = 2
        ; Update the privileges of this process with the new access token:
        r := DllCall(&quot;Advapi32.dll\AdjustTokenPrivileges&quot;, &quot;Ptr&quot;, t, &quot;Int&quot;, false, &quot;Ptr&quot;, ti.Ptr, &quot;UInt&quot;, 0, &quot;Ptr&quot;, 0, &quot;Ptr&quot;, 0)
        DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, t)  ; close this access token handle to save memory
        DllCall(&quot;CloseHandle&quot;, &quot;Ptr&quot;, h)  ; close this process handle to save memory
        return r
    }


    ; Method:  isTargetProcess64Bit(PID, hProcess := &quot;&quot;, currentHandleAccess := &quot;&quot;)
    ;          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 &amp; (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 &amp;&amp; DllCall(&quot;IsWow64Process&quot;, &quot;Ptr&quot;, hProcess, &quot;Int*&quot;, &amp;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(&amp;aModules, useFileNameAsKey := False)
    {
        if (A_PtrSize = 4 &amp;&amp; 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(&amp;lphModule)
            return -3
        loop moduleCount
        {
            aModuleInfo:=map()
            hModule := numget(lphModule, (A_index - 1) * A_PtrSize,&quot;Ptr&quot;)
            this.GetModuleInformation(hModule, &amp;aModuleInfo)
            aModuleInfo.Name := this.GetModuleFileNameEx(hModule)
            filePath := aModuleInfo.name
            SplitPath filePath, &amp;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&#039;t bother checking. Most coders just give it MAX_PATH size anyway
        VarSetStrCapacity(&amp;lpFilename,1024)
;        VarSetCapacity(lpFilename, 2048 * (A_IsUnicode ? 2 : 1))
        DllCall(&quot;psapi\GetModuleFileNameEx&quot;
                    , &quot;Ptr&quot;, this.hProcess
                    , &quot;Ptr&quot;, hModule
                    , &quot;Str&quot;, lpFilename
                    , &quot;Uint&quot;, 1024)
        if fileNameNoPath
            SplitPath lpFilename, &amp;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(&amp;lphModule, dwFilterFlag := 0x03)
    {
;        lastError := A_LastError
        size := 4 ; VarSetCapacity(lphModule, 4)
        reqSize:=0
        loop
        {
            if !DllCall(&quot;psapi\EnumProcessModulesEx&quot;
                        , &quot;Ptr&quot;, this.hProcess
                        , &quot;Ptr&quot;, lphModule.Ptr
                        , &quot;Uint&quot;, size
                        , &quot;Uint*&quot;, &amp;reqSize
                        , &quot;Uint&quot;, dwFilterFlag)
                return 0
            else if (size &gt;= 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(&quot;SetLastError&quot;, &quot;UInt&quot;, lastError)
        return reqSize // A_PtrSize ; module count  ; sizeof(HMODULE) - enumerate the array of HMODULEs
    }

    GetModuleInformation(hModule,&amp;aModuleInfo)
    {
        MODULEINFO:=buffer(A_PtrSize * 3)
;        VarSetCapacity(MODULEINFO, A_PtrSize * 3)
;        aModuleInfo := []
        r:=DllCall(&quot;psapi\GetModuleInformation&quot;
                    , &quot;Ptr&quot;, this.hProcess
                    , &quot;Ptr&quot;, hModule
                    , &quot;Ptr&quot;, MODULEINFO.Ptr
                    , &quot;UInt&quot;, A_PtrSize * 3)
        aModuleInfo := {  lpBaseOfDll: numget(MODULEINFO, 0, &quot;Ptr&quot;)
                       ,  SizeOfImage: numget(MODULEINFO, A_PtrSize, &quot;UInt&quot;)
                       ,   EntryPoint: numget(MODULEINFO, A_PtrSize * 2, &quot;Ptr&quot;) }
        return r
    }
}

</code></pre></div><p>Предупреждение!<br />Есть вероятность недочетов при переводе с AHK v1. <a href="https://forum.script-coding.com/viewtopic.php?id=15689">Версия для AHK v1</a>.<br /><a href="https://forum.script-coding.com/viewtopic.php?id=18368">Тема для обсуждения</a></p>]]></content>
			<author>
				<name><![CDATA[Alectric]]></name>
				<uri>https://forum.script-coding.com/profile.php?id=26027</uri>
			</author>
			<updated>2025-02-12T17:11:35Z</updated>
			<id>https://forum.script-coding.com/viewtopic.php?pid=161978#p161978</id>
		</entry>
</feed>
