It's to demonstrate potential syntax, facilitate other people wanting to do something similar, and may be useful template code to backport something similar in future from AHK v2.
I'm pretty flexible about the syntax that a built-in function might have, however, I think there are some elements/principles here that work quite well.
I think it's good to be able to specify offsets precisely, as is done here. However, a 'string to struct object' function, that calculates all of the offsets might also be good.
Do post any links, if there are any other attempts at struct classes. Thanks.
Code: Select all
;prototype Struct class
;==================================================
w:: ;test create struct
if !StructBasic.HasDef("RECT")
{
RECT := new StructBasic(16, 0)
RECT.Add("left",, "Int")
RECT.Add("top")
RECT.Add("right")
RECT.Add("bottom")
StructBasic.AddDef("RECT", RECT)
}
WinGet, hWnd, ID, A
RECT := new StructBasic("RECT")
DllCall("user32\GetWindowRect", "Ptr",hWnd, "Ptr",RECT.GetAddr())
WinGet, hWnd2, ID, ahk_class Shell_TrayWnd
RECT2 := new StructBasic("RECT")
DllCall("user32\GetWindowRect", "Ptr",hWnd2, "Ptr",RECT2.GetAddr())
vWinX := RECT.left
vWinY := RECT.top
vWinW := RECT.right - vWinX
vWinH := RECT.bottom - vWinY
vWinPos1 := Format("x{} y{} w{} h{}", vWinX, vWinY, vWinW, vWinH)
vWinX := RECT2.left
vWinY := RECT2.top
vWinW := RECT2.right - vWinX
vWinH := RECT2.bottom - vWinY
vWinPos2 := Format("x{} y{} w{} h{}", vWinX, vWinY, vWinW, vWinH)
MsgBox, % vWinPos1 "`r`n" vWinPos2
return
;==================================================
q:: ;test create custom struct
;create struct:
oStruct := new StructBasic(400, 0)
oStruct.Add("p1", 0, "Char")
oStruct.Add("p2", 8, "Short")
oStruct.Add("p3", 16, "Int")
oStruct.Add("p4", 24, "Int64")
oStruct.Add("p5", 100, "Str[32]")
oStruct.Add("p6", 200, "AStr[32]")
oStruct.Add("p7", 300, "WStr[32]")
if 0
{
oStruct := new StructBasic(400, 0)
;oStruct.Add("p1",, "Char")
;oStruct.Add("p2", 8, "Short") ;specify offset explicitly
oStruct.Add("p1",, "Char")
oStruct.Add("",, "AStr[7]") ;use padding to reach next offset
oStruct.Add("p2",, "Short")
oStruct.Add("p3", 16, "Int")
oStruct.Add("p4", 24, "Int64")
oStruct.Add("p5",, "Str[32]")
oStruct.Add("p6",, "AStr[32]")
oStruct.Add("p7",, "WStr[32]")
}
;use struct:
oStruct.p1 := 255
oStruct.p2 := 255
oStruct.p3 := 255
oStruct.p4 := 255
oStruct.p5 := "hello world" Chr(8730) ;square root sign
oStruct.p6 := "hello world" Chr(8730)
oStruct.p7 := "hello world" Chr(8730)
;oStruct.p1 := 111
;oStruct.SetValue("p1", 111) ;equivalent to line above
;MsgBox, % oStruct.p1
;MsgBox, % oStruct.GetValue("p1") ;equivalent to line above
if 0
{
MsgBox, % oStruct.p1 ;-1
MsgBox, % oStruct.p2
MsgBox, % oStruct.p3
MsgBox, % oStruct.p4
MsgBox, % oStruct.p5
MsgBox, % oStruct.p6
MsgBox, % oStruct.p7
oStruct.p1 := 123
;oStruct.p5 := "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ;didn't work
oStruct.p5 := "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
MsgBox, % oStruct.p1
MsgBox, % oStruct.p5
}
vOutput := ""
Loop, 7
{
vName := "p" A_Index
vOutput .= vName "`t" oStruct.GetSize(vName) "`t" oStruct.GetOffset(vName) "`t" oStruct.GetType(vName) "`t" oStruct[vName] "`r`n"
}
MsgBox, % vOutput
return
;==================================================
;oStruct := new StructBasic(vSize, 0)
;oStruct.Add(vName, vOffset, vType)
;types: Char, Short, Int, Int64, Ptr
;types: UChar, UShort, UInt, UInt64, UPtr
;types: n (n bytes) (not implemented)
;types: Str[n], AStr[n], WStr[n]
;note: each time an item is added, the pointer is moved after it
;if offset omitted, pointer is used
;if name omitted, pointer is moved, no item is added
;if type omitted, type of previous added item is used
class StructBasic
{
static _templates := Object()
__New(vSize, vFillByte:="")
{
local
global StructBasic
if StructBasic._templates.HasKey(vSize)
return ObjClone(StructBasic._templates[vSize])
ObjRawSet(this, "_offsets", Object())
ObjRawSet(this, "_types", Object())
ObjRawSet(this, "_sizes", Object())
this.SetCapacity("_buf", vSize)
this._size := vSize
if !(vFillByte = "")
DllCall("kernel32\RtlFillMemory", "Ptr",this.GetAddress("_buf"), "UPtr",vSize, "UChar",vFillByte)
this._ptr := 0
this._typelast := ""
this._sizelast := ""
}
_TypeRawToTypeSize(vTypeRaw, ByRef vType, ByRef vSize)
{
local
static oSizes := Object("Char",1, "Short",2, "Int",4, "Int64",8, "Ptr",A_PtrSize, "UChar",1, "UShort",2, "UInt",4, "UInt64",8, "UPtr",A_PtrSize)
static vChrSize := A_IsUnicode ? 2 : 1
if oSizes.HasKey(vTypeRaw)
vSize := oSizes[vTypeRaw]
, vType := vTypeRaw
else if RegExMatch(vTypeRaw, "i)^(W|A|)Str\[\d+]$") ;WStr[n], AStr[n], Str[n]
{
vSize := RegExReplace(vTypeRaw, "\D")
vSize *= (vTypeRaw ~= "i)^S") ? vChrSize
: (vTypeRaw ~= "i)^W") ? 2
: 1
vType := RegExReplace(vTypeRaw, "\[.*") ;WStr/AStr/Str
}
else
throw Exception("Invalid type: " vTypeRaw, -1)
}
Add(vName:="", vOffset:="", vTypeRaw:="")
{
local
static vChrSize := A_IsUnicode ? 2 : 1
if (vOffset = "")
vOffset := this._ptr
else
this._ptr := vOffset
if (vTypeRaw = "")
vType := this._typelast
, vSize := this._sizelast
else
this._TypeRawToTypeSize(vTypeRaw, vType, vSize)
if !(vName = "")
{
ObjRawSet(this._offsets, vName, vOffset)
ObjRawSet(this._types, vName, vType)
ObjRawSet(this._sizes, vName, vSize)
}
this._ptr += vSize
this._typelast := vType
this._sizelast := vSize
}
GetSize(vName:="")
{
local
if (vName = "")
return this._size
return this._sizes[vName]
}
GetType(vName)
{
local
return this._types[vName]
}
;might have called it GetAddress, but that would clash
GetAddr(vName:="")
{
local
vAddr := this.GetAddress("_buf")
if (vName = "")
return vAddr
return vAddr + this._offsets[vName]
}
GetOffset(vName:="")
{
local
if (vName = "")
return 0
return this._offsets[vName]
}
GetValue(vName)
{
local
return this[vName]
}
SetValue(vName, vValue)
{
local
this[vName] := vValue
}
_TypeSizeToEncLen(vType, vSize, ByRef vEnc, ByRef vLen)
{
local
static vEncDefault := A_IsUnicode ? "UTF-16" : "CP0"
static vChrSize := A_IsUnicode ? 2 : 1
if (vType = "Str")
vLen := vSize/vChrSize, vEnc := vEncDefault
else if (vType = "WStr")
vLen := vSize/2, vEnc := "UTF-16"
else if (vType = "AStr")
vLen := vSize, vEnc := "CP0"
}
__Get(vName)
{
local
;if (vName = "")
; throw Exception("Unknown property.", -1)
if 1
{
vAddr := this.GetAddress("_buf")
vOffset := this._offsets[vName]
vType := this._types[vName]
if InStr(vType, "Str")
{
vSize := this._sizes[vName]
this._TypeSizeToEncLen(vType, vSize, vEnc, vLen)
return StrGet(vAddr+vOffset, vLen, vEnc)
}
return NumGet(vAddr+0, vOffset, vType)
}
}
__Set(vName, vValue)
{
local
if !(vName = "_ptr")
&& !(vName = "_size")
&& !(vName = "_typelast")
&& !(vName = "_sizelast")
{
vAddr := this.GetAddress("_buf")
vOffset := this._offsets[vName]
vType := this._types[vName]
if InStr(vType, "Str")
{
vSize := this._sizes[vName]
this._TypeSizeToEncLen(vType, vSize, vEnc, vLen)
return StrPut(vValue, vAddr+vOffset, vLen, vEnc)
}
return NumPut(vValue, vAddr+0, vOffset, vType)
}
}
AddDef(vStructName, oStruct)
{
local
global StructBasic
ObjRawSet(StructBasic._templates, vStructName, ObjClone(oStruct))
}
HasDef(vStructName)
{
local
global StructBasic
return StructBasic._templates.HasKey(vStructName)
}
}
;==================================================
GET/SET FUNCTIONALITY
The class doesn't cover bit fields since AHK doesn't have bit field functionality.
Although I did write this:
BitGet/BitPut (NumGet/NumPut for bits) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=59736
Also, if AHK had functionality to get/put hex/base64/CLSID strings, that would be useful. E.g. HexGet/Base64Get/CLSIDGet (and XXXPut).
And further to that, functionality to write/read an array of strings to/from a null-separated list, would be useful. E.g. NSLGet and NSLPut.
See an NSLGet function here:
list/enable/disable USB devices - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=6&t=69380
CALCULATING STRUCT OFFSETS/SIZES
For a few structs, here, I left a mark stating '[CHECK]', for structs where I believed I had calculated the offsets/sizes correctly, but where Visual Studio was giving a different result. So please check those if you think you have some ideas.
list of structs with parameters (sizes and types) - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=74&t=30497
See also:
#pragma pack and struct offsets/sizes - AutoHotkey Community
https://autohotkey.com/boards/viewtopic.php?f=75&t=69790