prototype Struct class

Post your working scripts, libraries and tools
User avatar
jeeswg
Posts: 6904
Joined: 19 Dec 2016, 01:58
Location: UK

prototype Struct class

06 Nov 2019, 11:58

Here is a prototype simple Struct class, for creating structs and referring to the members as properties.

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
homepage | tutorials | wish list | fun threads | donate
WARNING: copy your posts/messages before hitting Submit as you may lose them due to CAPTCHA

Return to “Scripts and Functions”

Who is online

Users browsing this forum: No registered users and 42 guests