1 (изменено: Alectric, 2025-03-05 16:34:50)

Тема: AHK v2: Работа с Последовательным портом (COM Port)

Библиотека для работы с COM-портами в синхронном или асинхронном режимах.

Предназначена для обмена данными по Последовательным интерфейсам - COM Port. Функции, такие как: отправка данных в порт; получение ответных данных от порта; прослушка порта.
Позволяет работать в синхронном - однопоточном режиме, так и в асинхронном - многопоточном (в данном случае многопроцессовом) режиме.
Асинхронный режим предназначен для минимизации задержек при получении данных, для этого запускается второй процесс этого-же скрипта, который передает данные через "общую" память с помощью класса "ClassMemory" в основной процесс скрипта. Скрипт получает массив всех пройденных через порт пакетов и массив временных меток события прохождения пакета через порт. Для удобства обработки принятых данных добавлена возможность привязать функцию которая будет вызываться каждый раз при получении пакета данных.

+ Пример для синхронного режима
com10:=ASyncCOMPort()			; Создаем экземпляр объекта
com10.Connect(10)			; Подключаемся к COM10 с параметрами по умолчанию

if !com10.hPort
  msgbox "Error"

snd:=buffer(1000,0)			; Готовим буфер для отправки данных
rcv:=buffer(1000,0)			; Готовим буфер для приема данных

numput("Uint64",0x0102030405060708,snd) ; Готовим данные для отправки
com10.Write(snd,10)			; Отправляем
sleep 100				; Немного ждем
sz:=com10.Read(rcv,rcv.size)		; Получаем все принятые данные (если ответ был)

responce:=""				; Отображаем принятые данные
loop sz
  responce.=numget(rcv,a_index-1,"UChar") " "
msgbox responce
+ Пример для асинхронного режима
;----------------------------------------------;
#SingleInstance off				;
if (A_Args.Length=2)				;
{						;
  ASyncCOMPort()				 ; Эти строки всегда должны быть первыми в скрипте
  return					;
}						;
ComPort1:=ASyncCOMPort(100,,,,,,,OnRecieve)	; "ComPort1" можно использовать любое имя переменной
ComPort2:=ASyncCOMPort(100,,,,,,,OnRecieve)	; Любое количество экземпляров
;----------------------------------------------;
ComPort2.AceptSettings(10,38400,"E",8,2)
HotKey "Esc",fEsc ; !Не используй простые горячие клавиши (Esc::), используй команду "HotKey" в место этого! (HotKey "Esc",MyFunc)
OnExit Exit

dots:=""
COMPort:=""
while !COMPort
{
  sleep 1000
  dots.=">"
  if (strlen(dots)>20)
    dots:=""
  COMPort:=SearchCOM("USB")
  if !COMPort
    tooltip "COM Port не найден`nПоиск " dots
}
tooltip
ComPort1.AceptSettings(COMPort)

while !ComPort1.COMOk ; Ждем успешного подключения к порту
{
  tooltip "Пытаюсь подключиться к порту " ComPort1.PortNum " на скорости " ComPort1.BaudRate
  sleep 1000
}

HotKey "F1",f
tooltip "Нажми F1 чтобы отправить данные в порт.`nEsc для выхода."

f(*)
{
  snd:=buffer(8,0) ; Готовим буфер для отправки
  numput("Uint64",0x0A84010000000301,snd) ; Готовим данные для отправки (запрос Modbus на 1 holding регистр начиная с 0 регистра)
  ComPort1.SendData(snd) ; Отправляем запрос
  ComPort2.SendData([1,2,3,4,5,6,7,8,9,10]) ; Отправляем данные в другой порт, если нужно
}

OnRecieve(Port)
{
  Num:=Port.Package.Length
  sz:=Port.Package[Num].Length		; читаем размер последнего полученного пакета
  responce:=""				; выводим данные на экран
  loop sz
    responce.=Port.Package[Num][a_index] " "
  t:=FormatTime(Port.TimeStamp[Num][1],"dd.MM.yyyy hh:mm:ss")
  msgbox Port.PortNum "`nPackcage No" Num "`n" responce "`n" t "." Port.TimeStamp[Num][2]
}

fEsc(*)
{
  exitapp
}
exit(*)
{
  global
  ComPort1:=ComPort1.Delete()	; Корректное удаление объекта и очистка системной памяти
  ComPort2:=ComPort2.Delete()
  exitapp
}

/*
ASyncCOMPort by Alectric
https://forum.script-coding.com/
Static Method Call(Period:=0,PortNum:=0,BaudRate:=9600,Parity:="N",ByteSize:=8,StopBits:=1,ReadIT:=-1,OnRecieve:=0)
	Creates a new Object.
	If "Period" is presented uses multithread async mode, if not - simple sync mode. See examples below.
	Param "ReadIT" - value of ReadIntervalTimeOut and ReadTotalTimeoutConstant of COM port.
	If "ReadIT" param wasn't set it calculates automaticly.
	Param "OnRecieve" - Func object which will call when any packcage recieve.
	This function must accept at least 1 param - that will be "this" of an object or just use "*" as a param.

For multithread async mode only: -----------------------------------------------------------
Property Package:=[[1,2,3,4]]
	An array of arrays of COM port's buffer data, recieved and sended in order.

Property TimeStamp:=[[A_Now,ms,isSended]]
	An array of time stamps of each package and which package was sendded.

Method Clear()
	Clears both of arrays.

Property Period
	Change period of "Main" routine.

Property COMBusy
	Returns true if can't conect chousen COM Port.

Property GetCOMFail
	Returns last COMFail (see below).

Property GetCOMError
	Returns last COMError (see below).

Property COMOk
	Returns true if connection is Ok.

Property NDR
	Returns true if there is new data to read in "Package" array.

Property NewDataRecieved
	Returns true if there is new data to read and clears "NDR".

Property SlavePID
	PID of child process.

Method AceptSettings(PortNum:=-1,BaudRate:=-1,Parity:="",ByteSize:=-1,StopBits:=-1,ReadIT:=-1)
	Apply defined settings and reconnect the port.

Method SendData(Var,size:=0)
	Send data to port. Var could be an array or buffer or string.

Property Diskonnect
	Set to true to disconnect COM port and stop connect attemtions.

Method Delete()
	To correctly delete an object and free system memory use construction like this: Port:=Port.Delete()

For sync mode only: -------------------------------------------------------------------------------
Method Connect(PortNum:=-1,BaudRate:=-1,Parity:="",ByteSize:=-1,StopBits:=-1,ReadIT:=-1)
	Connect to COM port with params. Or reconnect with new params.
	Param "ReadIT" - see above.
	If params is blank or omitted - uses previously setted value.
	Param "PortNum" must always be presented at first connection attempt.
	Returns port's file handle if success or 0 at fail.
	If failed - use "COMFail" and "ErrorText" props to determine what the problem is.

Method Write(lpBuffer,nNumberOfBytesToWrite)
	Write data to COM port.
	Returns number of written bytes.

Method Read(lpBuffer,nNumberOfBytesToRead)
	Reads data from COM port.
	Returns number of readed bytes.

Method ClearRXBuffer()
	Flush COMport's RX buffer.

Method Close(h:=0)
	Close Handle.
	If call without param - closing object's COM file handle and disconnects COM port.

Property hPort - Returns COM file handle.

Property COMFail
	Inside class number of error. If 0 - all ok.

Property ErrorText
	Text of COMFail error.

Property COMError
	A_LastError of failed DLL.

For any mode: ----------------------------------------------------------------------
Method CalcTimeout(BaudRate,ByteSize:=8)
	Returns approximate ReadIntervalTimeout for chosen BaudRate.

Function CheckCOMPortExist(Port)
	Port could be a string ("COM10") or an integer.
	Returns true if COM port available.

Function SearchCOM(Name:="")
	Search available COM port by name or part of the name and retuns string like "COM10".
	Returns empty string if port not available.
	If call without param - returns list of available COM ports in array of {Port:COMPort,Name:FriendlyName}.

Examples:
;-----------------------------------------------------------------------------------
; Usage in simple sync mode:
  com10:=ASyncCOMPort()			; Create instance of communication
  com10.Connect(10)			; Connect to COM10 with default params

  if !com10.hPort
    msgbox "Error"

  snd:=buffer(1000,0)			; Prepare send buffer
  rcv:=buffer(1000,0)			; Prepare recieve buffer

  numput("Uint64",0x0102030405060708,snd) ; Prepare data to send
  com10.Write(snd,10)			; Do send
  sleep 100				; wait to respond
  sz:=com10.Read(rcv,rcv.size)		; Recieve all available data

  responce:=""				; show recieved data
  loop sz
    responce.=numget(rcv,a_index-1,"UChar") " "
  msgbox responce

;-----------------------------------------------------------------------------------
; Usage in multithread async mode:
;----------------------------------------------;
#SingleInstance off				;
if (A_Args.Length=2)				;
{						;
  ASyncCOMPort()				 ; Always must be the first strings of the script
  return					;
}						;
ComPort1:=ASyncCOMPort(100,,,,,,,OnRecieve)	; "ComPort1" could be any var name
ComPort2:=ASyncCOMPort(100,,,,,,,OnRecieve)	; Any num of instances
;----------------------------------------------;
ComPort2.AceptSettings(10,38400,"E",8,2)
HotKey "Esc",fEsc ; !Do not use simple hotkeys (Esc::), use HotKey command instead! (HotKey "Esc",MyFunc)
OnExit Exit

dots:=""
COMPort:=""
while !COMPort
{
  sleep 1000
  dots.=">"
  if (strlen(dots)>20)
    dots:=""
  COMPort:=SearchCOM("USB")
  if !COMPort
    tooltip "COM port not found`nSearching " dots
}
tooltip
ComPort1.AceptSettings(COMPort)

while !ComPort1.COMOk ; wait for connection
{
  tooltip "Trying to connect to " ComPort1.PortNum " with baud " ComPort1.BaudRate
  sleep 1000
}

HotKey "F1",f
tooltip "Press F1 to send data.`nEsc to exit."

f(*)
{
  snd:=buffer(8,0) ; Prepare buffer to send
  numput("Uint64",0x0A84010000000301,snd) ; prepare data to send (Modbus req for 1 holding register starting from 0)
  ComPort1.SendData(snd) ; Do send
  ComPort2.SendData([1,2,3,4,5,6,7,8,9,10]) ; Do send to another port
}

OnRecieve(Port)
{
  Num:=Port.Package.Length
  sz:=Port.Package[Num].Length		; read last packcage's size
  responce:=""				; show recieved data
  loop sz
    responce.=Port.Package[Num][a_index] " "
  t:=FormatTime(Port.TimeStamp[Num][1],"dd.MM.yyyy hh:mm:ss")
  msgbox Port.PortNum "`nPackcage No" Num "`n" responce "`n" t "." Port.TimeStamp[Num][2]
}

fEsc(*)
{
  exitapp
}
exit(*)
{
  global
  ComPort1:=ComPort1.Delete()	; correctly deleting the objects and clear system memory
  ComPort2:=ComPort2.Delete()
  exitapp
}
;----------------------------------------------------------------------
*/
class ASyncCOMPort
{
  __Delete()
  {
    if this.hPort
      this.Close()
    this.Memory:=""
    if !this.IsMain
      ExitApp
  }
  Delete()
  {
    settimer this.obm,0
    this.obm:=""
  }
  Period
  {
    Set
    {
      settimer this.obm,this._Period:=Value
    }
    get => this._Period
  }
  Baud_List:=[50,75,110,150,300,600,1200,1800
	,2000,2400,3600,4800,7200,9600,14400
	,19200,28800,38400,57600,115200,250000
	,300000,375000,500000,750000,1500000,3000000]
  Parity_List:=["N","E","O","M","S"]
  ByteSize_List:=[5,6,7,8]

  ; Memory offsets
  m:={   Diskonnect	:0	; UChar 1
	,ApplySettings	:1	; UChar 1
	,COMBusy	:2	; UChar 1
	,DataAvailable	:3	; UChar 1
	,SendDataNow	:4	; UChar 1
	,COMFail	:5	; UChar 1
	,COMError	:6	; Int   4
	,PortNum	:10	; Int   4
	,BaudRate	:14	; Int   4
	,Parity		:18	; Str   4	1 char
	,ByteSize	:22	; Int   4
	,StopBits	:26	; Int   4
	,ReadIT		:30	; Int   4
	,SendSize	:34	; Int   4
	,DataPos	:38	; Int   4
	,Packages	:42	; Int   4
	,SndOffset	:100	; Data  900
	,RcvOffset	:1000}	; Data  1047576

  __New(Period:=0,PortNum:=0,BaudRate:=9600,Parity:="N",ByteSize:=8,StopBits:=1,ReadIT:=-1,OnRecieve:=0)
  {
    if !IsInteger(PortNum)
      PortNum:=SearchCOM(PortNum)
    this.PortNum:=PortNum
    this.BaudRate:=BaudRate
    this.Parity:=Parity
    this.ByteSize:=ByteSize
    this.StopBits:=StopBits
    this.ReadIT:=ReadIT
    this.OnRecieve:=OnRecieve
    this.hPort:=0
    this.COMFail:=0
    this.ErrorText:=""
    this.obm:=0
    this.DataSize:=1048576
    this.ReserveMemory:=10240

    this.IsMain:=false
    if (!A_Args.Length and Period)
    { ; Main
      this.IsMain:=true
      this.COMBusy:=false
      this.COMOk:=false
      this.ReadPos:=0
      this.NDR:=false
      this.Package:=[]
      this.TimeStamp:=[]
      this.Data:=Buffer(this.DataSize,0xff)
      loop this.m.RcvOffset
        NumPut("UChar",0,this.Data,a_index-1)
      this.MainPID:=DllCall("GetCurrentProcessId")
      if A_IsCompiled
        run A_ScriptFullPath " " this.MainPID " " this.Data.Ptr,,,&SlavePID
      else
        run "`"" A_AhkPath "`" `"" A_ScriptFullPath "`" " this.MainPID " " this.Data.Ptr,,,&SlavePID
      this.SlavePID:=SlavePID
      if PortNum
        this.AceptSettings(PortNum,BaudRate,Parity,ByteSize,StopBits,ReadIT)
      this.obm:=ObjBindMethod(this,"Main")
      settimer this.obm,Period
    }
    else if (A_Args.Length=2)
    { ; Slave
      if (Period=0)
        Period:=1
      this.MainPID:=A_Args[1]
      this.pData:=A_Args[2]
      this.DataPos:=0
      this.Packages:=0
      obm:=ObjBindMethod(this,"CheckPIDExist")
      settimer obm,2000
      this.MSM:=ClassMemory("Ahk_PID" this.MainPID)
      this.Send_Data:=Buffer(this.m.RcvOffset-this.m.SndOffset)
      for k,v in this.m.OwnProps()
        this.m.%k%:=v+this.pData
      if PortNum
        this.Connect(PortNum,BaudRate,Parity,ByteSize,StopBits,ReadIT)
      this.obm:=ObjBindMethod(this,"Slave")
      settimer this.obm,Period
    }
    this.OldBaudRate:=this.BaudRate
    this._Period:=Period
    return this
  }
  CheckPIDExist()
  {
    if !ProcessExist(this.MainPID)
      this.__Delete()
  }

  Clear()
  {
    this.Package:=[]
    this.TimeStamp:=[]
  }

  AceptSettings(PortNum:=-1,BaudRate:=-1,Parity:="",ByteSize:=-1,StopBits:=-1,ReadIT:=-1)
  {
    if NumGet(this.Data,this.m.ApplySettings,"UChar")
      return
    if (PortNum!=-1)
      this.PortNum:=PortNum
    if IsInteger(this.PortNum)
      this.PortNum:="COM" this.PortNum
    if (BaudRate!=-1)
      this.BaudRate:=BaudRate
    if (Parity!="")
      this.Parity:=Parity
    if (ByteSize!=-1)
      this.ByteSize:=ByteSize
    if (StopBits!=-1)
      this.StopBits:=StopBits
    if !Timeout:=this.CalcTimeout(this.BaudRate,this.ByteSize)
      Timeout:=1
    if (this.ReadIT=-1 or (ReadIT=-1 and this.OldBaudRate!=this.BaudRate))
      this.ReadIT:=Timeout
    this.OldBaudRate:=this.BaudRate
    PortNum:=IsInteger(this.PortNum)?this.PortNum:RegExReplace(this.PortNum,"COM","")
    NumPut("Int",PortNum,this.Data,this.m.PortNum)
    NumPut("Int",this.BaudRate,this.Data,this.m.BaudRate)
    NumPut("Int",Ord(this.Parity),this.Data,this.m.Parity)
    NumPut("Int",this.ByteSize,this.Data,this.m.ByteSize)
    NumPut("Int",this.StopBits,this.Data,this.m.StopBits)
    NumPut("Int",this.ReadIT,this.Data,this.m.ReadIT)
    NumPut("UChar",0,this.Data,this.m.Diskonnect)
    NumPut("UChar",1,this.Data,this.m.ApplySettings)
  }

  SendData(Var,size:=0)
  {
    if (!this.COMOk or NumGet(this.Data,this.m.SendDataNow,"UChar")) ; отправка еще не закончена
      return
    max:=this.m.RcvOffset-this.m.SndOffset
    if (Var.base=[].base)
    {
      size:=size?size:Var.Length
      if (size>max)
      {
        Msgbox("Слишком много данных для отправки. " size " byte.`nМаксимально допустимо " max " byte.")
        return
      }
      loop size
        NumPut("UChar",Var[a_index],this.Data,this.m.SndOffset+a_index-1)
    }
    else if (Var.base=Buffer().base)
    {
      size:=size?size:Var.size
      if (size>max)
      {
        Msgbox("Слишком много данных для отправки. " size " byte.`nМаксимально допустимо " max " byte.")
        return
      }
      loop size
        NumPut("UChar",NumGet(Var,a_index-1,"UChar"),this.Data,this.m.SndOffset+a_index-1)
    }
    else if (!IsObject(Var) and !IsNumber(Var)) ; string
    {
      size:=size?size:StrLen(Var)
      if (size>max)
      {
        Msgbox("Слишком много данных для отправки. " size " byte.`nМаксимально допустимо " max " byte.")
        return
      }
      loop Parse Var
        NumPut("UChar",Ord(A_LoopField),this.Data,this.m.SndOffset+a_index-1)
    }
    NumPut("Int",size,this.Data,this.m.SendSize)
    NumPut("UChar",1,this.Data,this.m.SendDataNow)
    return true
  }

  Diskonnect
  {
    set
    {
      if this.IsMain
        NumPut("UChar",Value,this.Data,this.m.Diskonnect)
      else
        this.Close()
      return
    }
  }
  Main()
  {
    static _TimeStLen:=StrPut(A_Now,"cp0")
    if NumGet(this.Data,this.m.Diskonnect,"UChar")
    {
      this.COMOk:=false
      this.COMBusy:=false
      return
    }
    else if NumGet(this.Data,this.m.COMFail,"UChar")
    {
      NumPut("UChar",0,this.Data,this.m.COMFail)
      this.COMOk:=false
      this.COMBusy:=false
      this.AceptSettings()
    }
    else if NumGet(this.Data,this.m.COMBusy,"UChar")
    {
      NumPut("UChar",0,this.Data,this.m.COMBusy)
      this.COMOk:=false
      this.COMBusy:=true
    }
    else
    {
      this.COMOk:=true
      this.COMBusy:=false
;      ReadPack:=NumGet(this.Data,this.m.Packages,"Int")
      DataValid:=NumGet(this.Data,this.m.RcvOffset+this.ReadPos,"UChar")
      if (DataValid=0xFE)
      {
        this.ReadPos:=0
        return
      }
      else if (DataValid=1)
      {
        NumPut("UChar",0,this.Data,this.m.DataAvailable)
       pStatus:=this.m.RcvOffset+this.ReadPos+1
       pSize:=pStatus+1
        DataFromSlaveSize:=NumGet(this.Data,pSize,"UShort")
       pData:=pSize+2
       pTimeSt:=pData+DataFromSlaveSize
       pTimeMs:=pTimeSt+_TimeStLen
        if (DataFromSlaveSize<0 or DataFromSlaveSize>4096) ;DefaultBReq
        {
          this.ReadPos:=NumGet(this.Data,this.m.DataPos,"Int")
          return
        }
        NumPut("UChar",0XFF,this.Data,this.m.RcvOffset+this.ReadPos)
        NumPut("UShort",0xFFFF,this.Data,pSize)
        if (DataFromSlaveSize>0)
        {
          DataArray:=[]
          loop DataFromSlaveSize
          {
            addr:=pData+a_index-1
            DataArray.Push(NumGet(this.Data,addr,"UChar"))
            NumPut("UChar",0xFF,this.Data,addr)
          }
          TimeSt:=StrGet(this.Data.Ptr+pTimeSt,_TimeStLen,"cp0")
          loop _TimeStLen
            NumPut("UChar",0xFF,this.Data,pTimeSt+a_index-1)
          TimeMs:=NumGet(this.Data,pTimeMs,"UShort")
          NumPut("UShort",0xFFFF,this.Data,pTimeMs)
          Status:=NumGet(this.Data,pStatus,"UChar")
          NumPut("UChar",0xFF,this.Data,pStatus)
          this.ReadPos+=DataFromSlaveSize+_TimeStLen+6
          this.Package.Push(DataArray)
          this.TimeStamp.Push([TimeSt,TimeMs,Status=254?true:false])
          this.NDR:=true
          if (Status!=254 and this.OnRecieve.base=this.Main.base)
            this.OnRecieve()
        }
      }
    }
  }
  NewDataRecieved
  {
    get
    {
      if this.NDR
      {
        this.NDR:=false
        return true
      }
      return false
    }
  }
  GetCOMFail
  {
    get => NumGet(this.Data,this.m.COMFail,"UChar")
  }
  GetCOMError
  {
    get => NumGet(this.Data,this.m.COMError,"Int")
  }

  Slave()
  {
    static _TimeStLen:=StrPut(A_Now,"cp0")
    static StartTime:=0,Frequency:=0,EndTime:=0
    static EmptyPacket:=2,RIT:=0
    static In_Data:=Buffer(8000)
    static SleepTimer:=a_tickcount
	/*
	Array =
	-----------
	DataValid	+0	byte	1
	Status		+1	byte	1
	Size		+2	short	2
	Data byte 0	+4	byte	1
	Data byte 1	+5	byte	1
	Data byte 2	+6	byte	1
	Data byte n	+7	byte	1
	Time Stamp	+n	str	_TimeStLen
	Time ms		+str+n	short	2
	-----------
	Next		+2+str+n

	if Size == 0xFFFFFFFE
	  End
	*/
;tooltip this.PortNum
    if this.MSM.Read(this.m.Diskonnect,"UChar")
    {
;      this.Packages:=0
      this.Close()
    }
    else if this.MSM.Read(this.m.ApplySettings,"UChar")
    {
      this.MSM.write(this.m.ApplySettings,0,"UChar")
;      this.Packages:=0
      this.PortNum:=this.MSM.Read(this.m.PortNum,"Int")
      this.BaudRate:=this.MSM.Read(this.m.BaudRate,"Int")
      this.Parity:=Chr(this.MSM.Read(this.m.Parity,"Int"))
      this.ByteSize:=this.MSM.Read(this.m.ByteSize,"Int")
      this.StopBits:=this.MSM.Read(this.m.StopBits,"Int")
      this.ReadIT:=this.MSM.Read(this.m.ReadIT,"Int")
      ;tooltip this.PortNum "`n" this.BaudRate "`n" this.Parity "`n" this.ByteSize "`n" this.StopBits "`n" this.ReadIT
      this.Connect(this.PortNum
		,this.BaudRate
		,this.Parity
		,this.ByteSize
		,this.StopBits
		,this.ReadIT)
      if !this.hPort
      {
        this.MSM.write(this.m.COMBusy,1,"UChar")
        sleep 500
      }
    }
    if this.COMFail
    {
      this.MSM.write(this.m.COMFail,this.COMFail,"UChar")
      this.MSM.write(this.m.COMError,this.COMError,"Int")
      this.COMFail:=0
    }
    if this.hPort
    {
      if (this.MSM.Read(this.m.SendDataNow,"UChar")=1)
      {
        size:=this.MSM.Read(this.m.SendSize,"Int")
        if size
        {
          Send_Data:=Buffer(size)
          this.MSM.ReadRaw(this.m.SndOffset,Send_Data,size)
         pDataValid:=this.m.RcvOffset+this.DataPos
         pStatus:=pDataValid+1
         pSize:=pStatus+1
         pData:=pSize+2
         pTimeSt:=pData+size
         pTimeMs:=pTimeSt+_TimeStLen
          this.MSM.write(pStatus,254,"uChar")
          this.MSM.write(pSize,size,"uShort")
          this.MSM.WriteRaw(pData,Send_Data,size)
          str:=Buffer(_TimeStLen)
          TimeSt:=a_now
          TimeMs:=a_msec
          StrPut(TimeSt,str,_TimeStLen,"cp0")
          this.MSM.writeRaw(pTimeSt,str,_TimeStLen)
          this.MSM.write(pTimeMs,TimeMs,"UShort")
          this.DataPos+=6+_TimeStLen+size
          this.MSM.write(this.m.DataPos,this.DataPos,"Int")
          this.Packages++
          this.MSM.write(this.m.Packages,this.Packages,"Int")
          this.MSM.write(pDataValid,1,"UChar")
          this.ClearRXBuffer()
          this.Write(Send_Data,size)
        }
        this.MSM.write(this.m.SendDataNow,2,"UChar")
        DllCall("QueryPerformanceCounter","Int64*",&StartTime)
      }
      RCV_Size:=this.Read(In_Data,1)
      if RCV_Size
      {
        Offset:=RCV_Size
        Packet:=false
        EmptyPack:=0
        Status:=0
        DllCall("QueryPerformanceCounter","Int64*",&StartTime)
        loop (1000//this.ReadIT)
        {
          RCV_Size:=this.Read(In_Data.Ptr+Offset,4096)
          DllCall("QueryPerformanceFrequency","Int64*",&Frequency)
          DllCall("QueryPerformanceCounter","Int64*",&EndTime)
          RTimer:=(EndTime-StartTime)/Frequency*1000
          if RCV_Size
          {
            EmptyPack:=0
            Packet:=true
            DllCall("QueryPerformanceCounter","Int64*",&StartTime)
            Offset+=RCV_Size
          }
          else if Packet
            EmptyPack++
          TimeOut:=RTimer>(this.ReadIT*(EmptyPacket+4)) and RTimer>7
          Empty:=(EmptyPack=EmptyPacket)
          if (TimeOut or Empty)
          {
            if TimeOut
              Status:=255
            if Empty
              Status:=EmptyPack
            if TimeOut
            {
              if (++this.ReadIT>255)
                this.ReadIT:=1
              this.MSM.write(this.m.ReadIT,this.ReadIT,"Int")
              RIT:=0
            }
            else
            {
              if (++RIT>10)
              {
                RIT:=0
                if (--this.ReadIT<1)
                  this.ReadIT:=1
                this.MSM.write(this.m.ReadIT,this.ReadIT,"Int")
              }
            }
            this.MSM.write(this.m.SendDataNow,0,"UChar")
            break
          }
        }
        RCV_Size:=Offset
        str:=Buffer(_TimeStLen)
        TimeSt:=a_now
        TimeMs:=a_msec
        StrPut(TimeSt,str,_TimeStLen,"cp0")
       pDataValid:=this.m.RcvOffset+this.DataPos
       pStatus:=pDataValid+1
       pSize:=pStatus+1
       pData:=pSize+2
       pTimeSt:=pData+RCV_Size
       pTimeMs:=pTimeSt+_TimeStLen
        this.MSM.write(pStatus,Status,"UChar")
        this.MSM.write(pSize,RCV_Size,"UShort")
        this.MSM.WriteRaw(pData,In_Data,RCV_Size)
        this.MSM.writeRaw(pTimeSt,str,_TimeStLen)
        this.MSM.write(pTimeMs,TimeMs,"UShort")
        this.DataPos+=6+_TimeStLen+RCV_Size
        this.MSM.write(this.m.DataPos,this.DataPos,"Int")
        this.Packages++
        this.MSM.write(this.m.Packages,this.Packages,"Int")
        this.MSM.write(pDataValid,1,"UChar")
        RCV_Size:=0
        SleepTimer:=a_tickcount
      }
      else
      {
        DllCall("QueryPerformanceFrequency","Int64*",&Frequency)
        DllCall("QueryPerformanceCounter","Int64*",&EndTime)
        RTimer:=(EndTime-StartTime)/Frequency*1000
        if (RTimer>100)
        {
          this.MSM.write(this.m.SendDataNow,0,"UChar")
          DllCall("QueryPerformanceCounter","Int64*",&StartTime)
        }
      }
      if (a_tickcount-SleepTimer>30000)
        sleep 10

      if (this.DataPos>this.DataSize-(this.m.RcvOffset-this.pData)-this.ReserveMemory)
      {
       pDataValid:=this.m.RcvOffset+this.DataPos
        this.MSM.write(pDataValid,0xFE,"UChar")
        this.DataPos:=0
        this.MSM.write(this.m.DataPos,this.DataPos,"Int")
        this.Packages:=0
        this.MSM.write(this.m.Packages,this.Packages,"Int")
      }
    }
    else
      sleep 100
  }

  Write(lpBuffer,nNumberOfBytesToWrite)
  {
    lpNumberOfBytesWritten:=0
    Result:=DllCall("WriteFile"
         ,"Ptr",this.hPort
         ,"Ptr",IsInteger(lpBuffer)?lpBuffer:lpBuffer.Ptr
         ,"UInt",nNumberOfBytesToWrite
         ,"UPtr*",&lpNumberOfBytesWritten
         ,"Ptr",0) ; lpOverlapped
    If !Result
    {
      this.COMFail:=8
      this.ErrorText:=
	(
	"There is a problem with Serial Port communication.
	Failed Dll - WriteFile.
	LastError=" A_LastError
        )
      this.COMError:=A_LastError
      this.Close()
      return 0
    }
    return lpNumberOfBytesWritten
  }

  Read(lpBuffer,nNumberOfBytesToRead)
  {
    lpNumberOfBytesReceived:=0
    Result:=DllCall("ReadFile"
         ,"Ptr",this.hPort
         ,"Ptr",IsInteger(lpBuffer)?lpBuffer:lpBuffer.Ptr
         ,"UInt",nNumberOfBytesToRead
         ,"UPtr*",&lpNumberOfBytesReceived
         ,"Ptr",0) ; lpOverlapped
    If !Result
    {
      this.COMFail:=9
      this.ErrorText:=
	(
	"There is a problem with Serial Port communication.
	Failed Dll - ReadFile.
	LastError=" A_LastError
        )
      this.COMError:=A_LastError
      this.Close()
      return 0
    }
    return lpNumberOfBytesReceived
  }

  ClearRXBuffer()
  {
    Success:=DllCall("PurgeComm"
         ,"Ptr",this.hPort
         ,"Int",0x8) ; dwFlag PURGE_RXCLEAR=0x08; PURGE_TXCLEAR=0x04
    return Success
  }

  ClearCommError()
  {
    return DllCall("ClearCommError","Ptr",this.hPort)
  }

  CalcTimeout(BaudRate,ByteSize:=8)
  {
    return Floor(((ByteSize+4)/BaudRate)*1000)
  }

  Connect(PortNum:=-1,BaudRate:=-1,Parity:="",ByteSize:=-1,StopBits:=-1,ReadIT:=-1)
  {
    if (PortNum!=-1)
      this.PortNum:=PortNum
    if IsInteger(this.PortNum)
      this.PortNum:="COM" this.PortNum
    if (BaudRate!=-1)
      this.BaudRate:=BaudRate
    if (Parity!="")
      this.Parity:=Parity
    if (ByteSize!=-1)
      this.ByteSize:=ByteSize
    if (StopBits!=-1)
      this.StopBits:=StopBits

    if (ReadIT!=-1)
      this.ReadIT:=ReadIT
    if !Timeout:=this.CalcTimeout(this.BaudRate,this.ByteSize)
      Timeout:=1
    if (this.ReadIT=-1 or (ReadIT=-1 and this.OldBaudRate!=this.BaudRate))
      this.ReadIT:=Timeout
    this.OldBaudRate:=this.BaudRate

    if !CheckCOMPortExist(this.PortNum)
    {
      this.COMFail:=1
      this.ErrorText:="Port " this.PortNum " not exist."
      return 0
    }
    this.COMFail:=0
    this.ErrorText:=""
    if this.IsMain
      return true
    if this.hPort
      this.Close()
    lpDef:=	 "baud=" this.BaudRate .
		" parity=" this.Parity .
		" data=" this.ByteSize .
		" stop=" this.StopBits .
		" to=off xon=off odsr=off octs=off dtr=Off rts=off idsr=off"
    lpDCB:=Buffer(28,0)
    if !DllCall("BuildCommDCB","Str",lpDef,"Ptr",lpDCB.Ptr)
    {
      this.COMFail:=2
      this.ErrorText:=
	(
	"There is a problem with Serial Port communication.
	Failed Dll - BuildCommDCB.
	LastError=" A_LastError
        )
      this.COMError:=A_LastError
      return 0
    }
    hFile:=DllCall("CreateFile"
       ,"Str","\\.\" this.PortNum	;lpFileName
       ,"UInt",0xC0000000		;dwDesiredAccess GENERIC_READ=0x80000000 || GENERIC_WRITE=0x40000000
       ,"UInt",3			;dwShareMode FILE_SHARE_READ=0x01 || FILE_SHARE_WRITE=0x02
       ,"UInt",0			;lpSecurityAttributes
       ,"UInt",3			;dwCreationDisposition CREATE_NEW=0x01 || CREATE_ALWAYS=0x02
       ,"UInt",0			;dwFlagsAndAttributes
       ,"Ptr",0				;hTemplateFile
       ,"Ptr")
    if !hFile
    {
      this.COMFail:=3
      this.ErrorText:=
	(
	"There is a problem with Serial Port communication.
	Failed Dll - CreateFile.
	LastError=" A_LastError
        )
      this.COMError:=A_LastError
      return 0
    }
    if !DllCall("SetCommState","Ptr",hFile,"Ptr",lpDCB.Ptr)
    {
      this.COMFail:=4
      this.ErrorText:=
	(
	"There is a problem with Serial Port communication.
	Failed Dll - SetCommState.
	LastError=" A_LastError
        )
      this.COMError:=A_LastError
      this.Close(hFile)
      return 0
    }
    lpCommTimeouts:=Buffer(20,0) ; 5 * sizeof(DWORD)
    NumPut("UInt",this.ReadIT	,lpCommTimeouts, 0) ;ReadIntervalTimeout -  ms, max Interval betwen characters
    NumPut("UInt",0		,lpCommTimeouts, 4) ;ReadTotalTimeoutMultiplier - ms * reqested number of bytes
    NumPut("UInt",this.ReadIT	,lpCommTimeouts, 8) ;ReadTotalTimeoutConstant - ms
    NumPut("UInt",100		,lpCommTimeouts,12) ;WriteTotalTimeoutMultiplier - ms * sended number of bytes
    NumPut("UInt",10000		,lpCommTimeouts,16) ;WriteTotalTimeoutConstant - ms
    if !DllCall("SetCommTimeouts","Ptr",hFile,"Ptr",lpCommTimeouts.Ptr)
    {
      this.COMFail:=5
      this.ErrorText:=
	(
	"There is a problem with Serial Port communication.
	Failed Dll - SetCommTimeouts.
	LastError=" A_LastError
        )
      this.COMError:=A_LastError
      this.Close(hFile)
      return 0
    }
    return this.hPort:=hFile
  }

  Close(h:=0)
  {
    DllCall("CloseHandle","Ptr",(h?h:this.hPort))
    this.hPort:=0
  }

  WaitCommEvent(dwEvtMask:=0x3ff)
  {
  /*
  Events:
  EV_BREAK:=0x0040 ;A break was detected on input.
  EV_CTS:=0x0008 ;The CTS (clear-to-send) signal changed state.
  EV_DSR:=0x0010 ;The DSR (data-set-ready) signal changed state.
  EV_ERR:=0x0080 ;A line-status error occurred. Line-status errors are CE_FRAME, CE_OVERRUN, and CE_RXPARITY.
  EV_RING:=0x0100 ;A ring indicator was detected.
  EV_RLSD:=0x0020 ;The RLSD (receive-line-signal-detect) signal changed state.
  EV_RXCHAR:=0x0001 ;A character was received and placed in the input buffer.
  EV_RXFLAG:=0x0002 ;The event character was received and placed in the input buffer. The event character is specified in the device's DCB structure, which is applied to a serial port by using the SetCommState function.
  EV_TXEMPTY:=0x0004 ;The last character in the output buffer was sent.
  */
    MaskSet:=DllCall("SetCommMask"
         ,"Ptr",this.hPort
         ,"int",dwEvtMask)
    if !MaskSet
    {
      this.COMFail:=6
      this.ErrorText:=
	(
	"There is a problem with Serial Port communication.
	Failed Dll - SetCommMask.
	LastError=" A_LastError
        )
      this.COMError:=A_LastError
      this.Close()
      return 0
    }
    lpEvtMask:=Buffer(4,0)
    Success:=DllCall("WaitCommEvent"
         ,"Ptr",this.hPort
         ,"Ptr",lpEvtMask.Ptr
         ,"Ptr",0) ; lpOverlapped
    if !Success
    {
      this.COMFail:=7
      this.ErrorText:=
	(
	"There is a problem with Serial Port communication.
	Failed Dll - WaitCommEvent.
	LastError=" A_LastError
        )
      this.COMError:=A_LastError
      this.Close()
      return 0
    }
    return 1
  }
}

CheckCOMPortExist(Port)
{
  if isinteger(Port)
    Port:="COM" Port
  List:=SearchCOM()
  loop List.Length
  {
    o:=List[a_index]
    if (o.Port=Port)
      return true
  }
}
SearchCOM(Name:="")
{
  if !Name
    Out:=[]
  Loop Reg "HKLM\HARDWARE\DEVICEMAP\SERIALCOMM\"
  {
    COMPort:=RegRead()
    FriendlyName:=""
    Loop Reg "HKLM\SYSTEM\CurrentControlSet\Enum","KVR"
    {
      if (A_LoopRegName="PortName")
      {
        Outputvar:=RegRead()
        if (Outputvar=COMPort)
        {
          FriendlyName:=RegRead(RegExReplace(A_LoopRegKey, "(.*)\\Device Parameters", "$1"),"FriendlyName","")
          if (Name and InStr(FriendlyName,Name))
            return COMPort
        }
      }
    }
    if (FriendlyName="")
      FriendlyName:=A_LoopRegName
    if !Name
      Out.Push({Port:COMPort,Name:FriendlyName})
    else if (InStr(A_LoopRegName,Name))
      return COMPort
  }
  if !Name
    return Out
  return ""
}


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)
    {
        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("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)
                DetectHiddenWindows DHW
                return this
            }
        }
        DetectHiddenWindows DHW
        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 0
        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.

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