Тема: 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
}
}