Struct

Struct is a build-in function that creates and returns a structure object.
This object can be used to access the defined structure using object syntax.
SetCapacity Method can be used to allocate memory to structure and pointers.

Methods:

Struct requires a definition and accepts optionally address of structure memory and initialization object.

OutputVar := Struct(Definition ,StructMemory, InitObject)
Function Example: pt := Struct("int x;y",,{x:10,y:20})

Parameters

OutputVar

The name of the variable in which to store the Structure Object.

Definition

This parameter must be a string or variable containing the structure definition.
Definition is similar to C so most structures can be used directly or transformed very easily.
Following default data types can be used in Structures, other types mus be defined in script.
Either semicolon (;) or comma (,) can be used to separate fields, even mixed.
If no type for first field is given UInt will be used, otherwise previous type will be used.
E.g. in "a,Int x,y,Char c,d", a will be UInt, x and y Int, c and d Char.
If only one type is given, e.g. "UInt" or "POINT", it is assumed to be an array of one, same as "UInt[1]" or "POINT[1]"

Note! if definition contains a comma or semicolon, e.g. "len;" or "MyVar," it is interpreted as "UInt length" and "UInt MyVar",
where for Struct("MyVar"), MyVar must define a structure, e.g. MyVar := "Int x, Int y", if there is no MyVar variable or it is empty Struct() will throw an error.

Comments and few examples:

Creating a structure from a string containing the structure definition.

pt:=Struct("Int x,Int y")

The definition can be saved in a variable. Struct will also resolve the given string to the variable if necessary, however the structure will be created as array of 1 so Struct("POINT") is equal to Struct("POINT[1]").
This will change the syntax how to access the fields, for pt:=Struct(POINT) or pt:=Struct("UInt x,y") you can access the values directly, e.g. pt.x where for pt:=Struct("POINT") and pt:=Struct("POINT[1]") you will need to use pt.1.x or pt[1].x or pt["1"].x.
The evaluation order is always Structure -> Array -> Pointer -> etc. -> field.
So in following structure s:=Struct("*UInt") which is the same as s:=Struct("*UInt[1]"),
first array is accessed s.1, then pointer s.1.1.

POINT:="
(
Int x; // also comments in C syntax are supported
       // empty lines will be simply ignored.
Int y; // last ; is optional and can be omitted.
)"
POINT:="Int x,y"
pt:=Struct("POINT")  ; same as pt:=Struct("POINT[1]")

Without comments, new lines can be omitted too. This way the definition can be written much more compact.

POINT:="Int x,y"

UInt is default type so it can be omitted too if negative values are not relevant (for negative values Int must be used).

POINT:="x,y"

UNION AND STRUCT

Struct supports unions and structures in structures. Note, sub-structures must not have a name so you can't use same name for a field in main structure and sub-structures.
You can simply prefix those with the structure name, e.g. dummy_a, dummy_b....

UnionStruct:="
(
union {
  UInt int;
  struct {
    UShort x;
    UShort y;
  };
  struct {
    Byte a;
    Byte b;
    Byte c;
    Byte d;
  };
};
)"
mys:=Struct(UnionStruct)
mys.int:=0xFFFFFFFF
MsgBox mys.int "`n" mys.x " " mys.y "`n" mys.a " " mys.b " " mys.c " " mys.d
Global / Static / Local variables

We can create a structure from a static function variables outside the function or even in another function. Therefore include the name of the function and enclose variable in brackets. This is also necessary if you create a static structure from a static variable like in MyFunc here.

MyFunc() ; using this method we can create static structures
AnotherFunc() ; this method can be used anywhere to adccess the variable
pt:=Struct("MyFunc(POINT)",,{x:100,y:200}) ; even outside the function we can access the definition variable
MsgBox pt.x "-" pt.y
MyFunc(){
  static POINT:="UInt x,UInt y"
	, pt:=Struct("MyFunc(POINT)",,{x:10,y:20})
  MsgBox(pt.x "-" pt.y)
}
AnotherFunc(){
  static pt:=Struct("MyFunc(POINT)",,{x:10,y:20})
  MsgBox(pt.x "-" pt.y)
}
StructMemory

Buffer object or Address/pointer to memory representing the structure. This variable is used to access an existing structure in memory. For example here we would use the memory of variable pointMem for the structure.

pointMem:=BufferAlloc(8)
pt:=Struct("x,y",pointMem)
pt.x:=10
MsgBox pt[] " = " pointMem.Ptr "`n" pt.x " = " NumGet(pointMem,"UInt")
InitObject

Initialize your structure right away using an object, map, array or another structure.
The order of keys and values is not relevant.

pt:=Struct("x,y",,{x:100,y:200})
MsgBox pt.x "`n" pt.y

Methods

Clone

Returns new structure object of same type.

OutputVar := Struct.Clone()
OutputVar The name of the Variable in which to store the new structure object.
pt:=Struct("x,y")
pt1:=pt.Clone()
pt1[]:={x:10,y:20}
MsgBox pt1.x "-" pt1.y

CountOf

Returns size of array definition or 0 if structure or field is not an array.
Note! Array size can be increased using SetCapacity, however CountOf will always return defined value.

OutputVar := Struct.CountOf([field])
OutputVar The name of the Variable in which to store the length of array.
field Name of existing field within the structure..
uint:=Struct("UInt[10]")
MsgBox uint.CountOf() ; returns 10
pt:=Struct("UInt x[2],UInt y[2]")
MsgBox pt.CountOf("x") ; returns 2

pt:=Struct("Uint a[2]")
MsgBox pt.a.CountOf() ; returns 2
pt.SetCapacity(16)
MsgBox pt.a.CountOf() ; will still return 2

Encoding

Returns encoding for a field.

OutputVar := Struct.Encoding([field])
OutputVar The name of the Variable in which to store the encoding.
field Name of existing field within the structure.
If type of field or structure is not one of String types (TCHAR, CHAR, UCHAR, LPTSTR...) -1 is returned. Otherwise it returns 0 for CP0 and 1200 for UTF-16 ...
Other encoding types have to use StrGet and StrSet to retrieve correct text.
str1:=Struct("LPSTR name")
str2:=Struct("LPTSTR name")
MsgBox str1.Encoding("name") " != " str2.Encoding("name")

GetAddress

Returns address of field or structure.

OutputVar := Struct.GetAddress([field])
OutputVar The name of the Variable in which to store the address.
field Name of existing field within the structure. When omitted, returns address of structure itself. To get the address for structure you can also use[] or [""] for structure objects and [""] for its fields. Note you cannot use [] for fields.
pt:=Struct("x,y")
MsgBox pt.GetAddress() " = " pt[]
MsgBox pt.GetAddress("y") " = " pt.y[""]

GetCapacity

Returns Capacity previously allocated using .SetCapacity() or by assigning a string.

OutputVar := Struct.GetCapacity([field])
OutputVar The name of the Variable in which to store the capacity in bytes.
field Name of existing field in our structure.
str:=Struct("LPTSTR name")
str.SetCapacity("name",2000)
MsgBox str.GetCapacity("name")

GetPointer

Returns the pointer for allocated memory saved in structure or field.

OutputVar := Struct.GetPointer([field])
OutputVar The name of the Variable in which to store the address.
field Name of existing field within the structure. When omitted, returns pointer of first item in the structure.
str:=Struct("LPTSTR name",,{name:"AutoHotkey"})
MsgBox str.GetPointer("name") "`n" StrGet(str.GetPointer("name"))

You can also use "" to read the pointer. So [""] returns address and ["",""] returns the pointer, ["","",""] pointer to pointer and so on.

str:=Struct("LPTSTR name",,{name:"AutoHotkey"})
MsgBox str.name["",""] "`n" StrGet(str.name["",""])

IsPointer

Returns true if the field or structure is a pointer.

OutputVar := Struct.IsPointer([field])
OutputVar The name of the Variable in which to store true if field or structure is a pointer or 0 / false otherwise.
field Name of existing field within the structure. When omitted, returns true if structure itself is a pointer.
s:=Struct("UInt *a,UInt b")
MsgBox s.IsPointer("a") " != " s.IsPointer("b")
s:=Struct("UInt*")
MsgBox s.IsPointer()

Offset

Returns offset for a field.

OutputVar := Struct.Offset(field)
OutputVar The name of the Variable in which to store the offset.
field Name of existing field within the structure.
pt:=Struct("x,y")
MsgBox pt.Offset("y") ; returns 4
; Note! because structure resolves pointers and arrays dynamically, offset for those will be relative to parent item
MyStruct:="Int a,b"
pt:=Struct("MyStruct a[2]")
MsgBox pt.a.Offset(2)  ; returns 8
       . " / " pt.a.2.Offset("b") ; returns 4

SetCapacity

Allocate memory for a field, returns allocated size if new memory was allocated.

OutputVar := Struct.SetCapacity([field,] newsize)
OutputVar The name of the Variable in which to store the new size.
field Name of the field where memory should be allocated.
new size Must be a digit or a variable containing a digit that represents new size of memory to be allocated.
str:=Struct("LPTSTR name")
str.SetCapacity("name",2000)
previous_pointer := str.GetPointer("name")
str.name:="AutoHotkey"
MsgBox previous_pointer " = " str.GetPointer("name") "`n" str.name ; as you can see pointer did not change since memory was only reduced.
MsgBox str.name.GetCapacity() ; however, allocated size changed.

When memory is reallocated the content will be copied to new memory. When a string is assigned to a field the memory is reallocated unless the size is exactly the needed size for the string + terminator.

str:=Struct("LPTSTR name")
str.SetCapacity("name",100)
MsgBox str.GetCapacity("name")
str.name:="AutoHotkey"
MsgBox str.GetCapacity("name")

Size

Returns the size in bytes of a structure or field.

OutputVar := Struct.Size([field])
OutputVar The name of the Variable in which to store the size of field or structure.
field Name of existing field within the structure, if omitted the size of structure is returned.
pt:=Struct("x,y")
MsgBox pt.Size() ; returns 8
struct:=Struct("Int64 x,y")
MsgBox struct.Size("y") ; returns 8
If structure is an array you will needle to pass a digit to retrieve the size of a field.
struct:=Struct("Int64[2]")
MsgBox struct.Size(1) ; returns 8

Features and Remarks

Some remarks about structure objects and more features.
By default, structure is aligned to the largest following item, however this can be changed by specifying the alignment followed by : in beginning of structure.
For example sizeof("Int a,Int64 b") == 16, where sizeof("4:Int a, Int64 b") == 12.
Ptr, Base and __Class are reserved properties and cannot be accessed using dot (.) syntax, avoid using them as field or use MyStruct["Ptr"], MyStruct["base"] and MyStruct["__Class"] to access them!!!
A structure object can be resized, so you can even create array of a structure by increasing allocated memory.
You can receive the address of structure or key using empty key (e.g. MyStruct.item[]). Same can be used for structure objects too (e.g. MyStruct[] or MyStruct[""]).

When a key is not given a type, e.g. "LPTSTR key1,key2", previous type is used. If the first key lacks a type, Uint is used, so "key1,key2" is equivalent to "UInt key1,key2" or "UInt key1,UInt key2".

Note: pointer needs to be specified for each element, so "UInt *key1,key2" is equivalent to "UInt *key1,UInt key2". If both elements are pointers "UInt *key1,*key2" must be used.

To access a pointer in pointer you can specify empty key multiple times, e.g. MyStruct["",""] would get the pointer at address of MyStruct.
Same is valid for keys, e.g. MyStruct.key["",""].
s:=Struct("LPTSTR str")
s.str:="Hello World!"
MsgBox StrGet(s.str["",""])
Type only structures

Struct supports type only strcuctures for all default types like Int,Byte,Char... .
Those will be created as array, so "char" is equivalent to "char[1]". To access the field of such structures you will always need to use struct.1 or struct[1] or struct["1"].

u:=Struct("UInt") ; equivalent to UInt[1]
u.1:=10
MsgBox u.1
Arrays

Same way arrays are supported.

u:=Struct("UInt[10]")
u.10:=100
MsgBox u.10
Pointers

Struct also supports pointers.

mystruct:=Struct("*int") ; same as "*int[1]"
mystruct.SetCapacity(1,8)
mystruct.1.1:=100
MsgBox mystruct.1.1
Custom and default fields and structures

Struct supports custom structures and fields.

POINT:="Int x, Int y"
pt:=Struct("POINT p",,{ p: { x:10, y:20 } } )
MsgBox pt.p.x " , " pt.p.y
Also pointers are supported, however before the field can be accessed we have to allocate memory to it.
POINT:="Int x, Int y"
pt:=Struct("POINT *p")
pt.SetCapacity("p",sizeof(pt.p))
pt.p.x:=100, pt.p.y:=200
MsgBox pt.p.x " , " pt.p.y

The memory will be managed internally and freed whenever the object is deleted.
For strings memory will be initialized automatically before.

s:=Struct("LPTSTR string")
s.string:="Hello World!"
MsgBox s.string

The size of allocated memory can be retrieved with .GetCapacity() method

MsgBox s.GetCapacity("string")

Whenever a new string is assigned, memory will be reallocated if the new string is not same lengths.
To free the memory manually we can use .SetCapacity().

s.SetCapacity("string",0)
MsgBox s.GetCapacity("string")

You can manually allocate memory to fields using .SetCapacity() method.
However, to keep allocated memory you have to use StrPut("new string",s.string[""]) to write the string, otherwise the memory will be reallocated if new string is different length.

s:=Struct("LPTSTR string")
s.SetCapacity("string",260)
BIT FIELDS

Also bit fields are supported, see Bit Fields for more information.

Bits:=Struct("
(
  {
    Byte int;
    struct {
      Byte a:1,b:1,c:1,d:1,e:1,f:1,g:1,h:1;
    }
  }
)")
Loop 0xFF {
  bit:=(Bits.int:=A_Index) "`t"
  For k, v in Bits
    If A_Index>1
      bit.= v " "
  ToolTip "int   bits: 1 2 3 4 5 6 7 8`n" bit
  Sleep 200
}
ENUMERATING A STRUCTURE

Using the for loop we can enumerate the structure to retrieve field names and their values.
Enumeration will be executed in same order as the structure was defined.

s:=Struct("Byte x,Int u,LPTSTR str")
s.x:=10
s.u:=1000
s.str:="AutoHotkey"
for k,v in s
  MsgBox k ": " v

Also Arrays can be enumerated same way.

x:=Struct("UInt[10]",[9,8,7,6,5,4,3,2,1,0])
for k, v In x
  MsgBox k ": " v

Note! Fields of unknown size like "Uint *a" can be only enumerated to first value and not deeper!
To enumerate the structure completeley it needs to be defined completely!

MyStruct:="UInt[3]"
pt:=Struct("MyStruct *a")
pt.a.SetCapacity(1,100)
pt.a.1:=[100,200,300]
for k,v in pt.a.1
    MsgBox k "=" v

Related

sizeof, StructTypes

Examples

; 				Create array from existing memory
_POINT:="x,y"
pt:=Struct("*_POINT") ; similar to "*_POINT[1] but we have to allocate memory"
pt.SetCapacity(1,A_PtrSize),pt.SetCapacity(2,A_PtrSize)
pt.1.SetCapacity(1,8),pt.2.SetCapacity(1,8) ; allocate memory
pt.1.1.x:=100, pt.2.1.x:=200
MsgBox pt.1.1.x " / " pt.2.1.x
; 				More examples
pt:=Struct("x,y") ;POINT structure
pt.x:=100
MsgBox pt.x
rc:=Struct("left,top,right,bottom") ; RECT structure
Gui:=GuiCreate()
Gui.Show("w640 h480")
DllCall("GetWindowRect","PTR",gui.hwnd,"PTR",rc[])
MsgBox "left: " rc.left "`ntop: " rc.top "`nright: " rc.right "`nbottom: " rc.bottom
Gui.Destroy
; 				Array Examples
; Simple array structures.
; Array is always accessed using integer
arr:=Struct("Uint[10]")
arr.5:=10
MsgBox arr.5
MyArray:="a,b"
arr:=Struct("MyArray[10]")
arr.1.a:=1
arr.2.b:=2
MsgBox arr.1.a " / " arr.2.b
; 				Pointer Examples
; SIMPLE POINTER*
int:=Struct("*UInt")
int.SetCapacity(1,4)
int.1.1:=100
MsgBox int.1.1
; 				Pointer to array of pointers
s:=Struct("**UInt")
s.SetCapacity(1,8) ; array of 2 to pointer to uint
s.1.SetCapacity(1,8),s.1.SetCapacity(2,8)
s.1.1.SetCapacity(1,8),s.1.2.SetCapacity(1,8)
s.1.1.1:=10
s.1.1.2:=20
s.1.2.1:=30
s.1.2.2:=40
MsgBox s.1.1.1 "`n" s.1.1.2 "`n"  s.1.2.1 "`n"  s.1.2.2
s[]:=[[[50,60],[70,80]]]
MsgBox s.1.1.1 "`n" s.1.1.2 "`n"  s.1.2.1 "`n"  s.1.2.2
; 				String Examples
; Simple user defined structure
user:="UInt Id, LPTSTR Name"
users := Struct("user[2]") ; array of structs
users.1.Id := 1 ,users.2.Id := 2
users.1.Name := "Admin" ,users.2.Name := "User"
MsgBox users.1.Id "`t" users.1.Name "`n" users.2.Id "`t" users.2.Name
; we can use an object to assign values too
users[]:=[{id:2,name:"Struct"},{id:2,name:"Object"}]
MsgBox users.1.Id "`t" users.1.Name "`n" users.2.Id "`t" users.2.Name
; 				Char array
String:=Struct("TCHAR char[26]")
Loop 26
 string["char"][A_Index]:=Chr(A_Index+64)
Loop 3
 MsgBox String["char"][A_Index*2] ;show some characters
MsgBox StrGet(string[],26) ;get complete string
; 				rect example
Gui:=GuiCreate()
_RECT:="left,top,right,bottom"
RC:=Struct(_RECT) ;create structure
Gui.Add("Text",,"Press Escape to continue")
Gui.Show("w200 h100") ;show window
DllCall("GetWindowRect","PTR",Gui.hwnd,"PTR",rc[]) ;get window position
rc.right := rc.right - rc.left ;Set rc.right to be the width
rc.bottom := rc.bottom - rc.top ;Set rc.bottom to be the height
While DllCall("GetCursorPos","PTR",rc[]) {
 DllCall("MoveWindow","PTR",gui.hwnd,"int",rc.left,"int",rc.top,"int",rc.right,"int",rc.bottom,"Int",1)
 If GetKeyState("Escape","P")
   break
}
Gui.Destroy
; 				Findfirstfile example
_FILETIME := "dwLowDateTime,dwHighDateTime"
_SYSTEMTIME := "WORD wYear,WORD wMonth,WORD wDayOfWeek,WORD wDay,WORD wHour,WORD wMinute,WORD wSecond,WORD Milliseconds"
_WIN32_FIND_DATA := "dwFileAttributes,_FILETIME ftCreationTime,_FILETIME ftLastAccessTime,_FILETIME ftLastWriteTime,UInt nFileSizeHigh,nFileSizeLow,dwReserved0,dwReserved1,TCHAR cFileName[260],TCHAR cAlternateFileName[14]"

file:=Struct("_WIN32_FIND_DATA[2]")
time:=Struct(_SYSTEMTIME)
DllCall("FindFirstFile","Str",A_ScriptFullPath,"Uint",file.1[""])
DllCall("FindFirstFile","Str",A_AhkPath,"UInt",file.2[""])
MsgBox StrGet(file.1.cFileName[""])
MsgBox "A_ScriptFullPath:`t" StrGet(file.1.cFileName[""]) "`t" StrGet(file.1.cAlternateFileName[""]) "`nA_AhkPath:`t" StrGet(file.2.cFileName[""]) "`t" StrGet(file.2.cAlternateFileName[""])

handle:=DllCall("FindFirstFile","Str","C:\*","Uint",file.2[""])
Loop {
   If !DllCall("FindNextFile","Uint",handle,"Uint",file.2[""])
      break
   DllCall("FileTimeToSystemTime","Uint",file.2.ftLastWriteTime[""],"Uint",time[""])
   ToolTip StrGet(file.2.cFileName[""]) "`n" StrGet(file.2.cAlternateFileName[""]) "`n" file.2.nFileSizeHigh " - " file.2.nFileSizeLow
         . "`n" time.wYear . "-" time.wMonth . "-" time.wDay
         . "`n" time.wDayOfWeek
         . "`n" time.wHour . ":" time.wMinute   . ":" time.wSecond . ":" time.Milliseconds
   Sleep 200
}
ToolTip
DllCall("FindClose","Uint",handle)

; 				Process32first example
MAX_PATH:=260
_PROCESSENTRY32:=
(
  "DWORD     dwSize;
  DWORD     cntUsage;
  DWORD     th32ProcessID;
  ULONG_PTR th32DefaultHeapID;
  DWORD     th32ModuleID;
  DWORD     cntThreads;
  DWORD     th32ParentProcessID;
  LONG      pcPriClassBase;
  DWORD     dwFlags;
  TCHAR     szExeFile[" MAX_PATH "];"
)
pEntry:= Struct(_PROCESSENTRY32)
pEntry.dwSize := sizeof(_PROCESSENTRY32)
hSnapshot:=DllCall("CreateToolhelp32Snapshot","UInt",TH32CS_SNAPALL:=0x0000001F,"PTR",0)
DllCall("Process32First" (A_IsUnicode?"W":""),"PTR",hSnapshot,"PTR",pEntry[""])
While (A_Index=1 || DllCall("Process32Next" (A_IsUnicode?"W":""),"PTR",hSnapshot,"PTR",pEntry[""])) {
  ToolTip pEntry.cntUsage "`n" pEntry.th32ProcessID
  . "`n" pEntry.th32DefaultHeapID "`n" pEntry.th32ModuleID
  . "`n" pEntry.cntThreads "`n" pEntry.th32ParentProcessID
  . "`n" pEntry.pcPriClassBase "`n" pEntry.dwFlags "`n" StrGet(pEntry.szExeFile[""])
  Sleep 150
}
ToolTip
; 				Listprocessmodules example
MAX_PATH:=260
        MAX_MODULE_NAME32:=255
        _MODULEENTRY32:=
        (
          "DWORD   dwSize;
          DWORD   th32ModuleID;
          DWORD   th32ProcessID;
          DWORD   GlblcntUsage;
          DWORD   ProccntUsage;
          BYTE    *modBaseAddr;
          DWORD   modBaseSize;
          HMODULE hModule;
          TCHAR   szModule[" MAX_MODULE_NAME32 + 1 "];
          TCHAR   szExePath[" MAX_PATH "];"
        )
        ListProcessModules(DllCall("GetCurrentProcessId"))
Return

ListProcessModules(dwPID)
{
  global _MODULEENTRY32
  static TH32CS_SNAPMODULE:=0x00000008,INVALID_HANDLE_VALUE:=-1
  me32 := Struct(_MODULEENTRY32)

  ;  Take a snapshot of all modules in the specified process.
  hModuleSnap := DllCall("CreateToolhelp32Snapshot","UInt", TH32CS_SNAPMODULE,"PTR", dwPID )
  if( hModuleSnap = INVALID_HANDLE_VALUE )
  {
    MsgBox "CreateToolhelp32Snapshot (of modules)"
    return FALSE
  }

  ; Set the size of the structure before using it.
  me32.dwSize := sizeof("_MODULEENTRY32")

  ;  Retrieve information about the first module,
  ;  and exit if unsuccessful

  if( !DllCall("Module32First" (A_IsUnicode?"W":""),"PTR", hModuleSnap,"PTR", me32[] ) )
  {
    MsgBox "Error Module32First`n" ErrorMessage() ;  // Show cause of failure
    DllCall("CloseHandle","PTR", hModuleSnap ) ;     // Must clean up the snapshot object!
    return  FALSE
  }

  ;//  Now walk the module list of the process,
  ;//  and display information about each module
  while(A_Index=1 || DllCall("Module32Next" (A_IsUnicode?"W":""),"PTR",hModuleSnap,"PTR", me32[""] ) )
  {
    ToolTip "`tMODULE NAME`t=`t"       StrGet(me32.szModule[""])
            . "`n`texecutable`t=`t"    StrGet(me32.szExePath[""])
            . "`n`tprocess ID`t=`t"    me32.th32ProcessID
            . "`n`tref count (g)`t=`t"   me32.GlblcntUsage
            . "`n`tref count (p)`t=`t" me32.ProccntUsage
            . "`n`tbase address`t=`t"    me32.modBaseAddr[""]
            . "`n`tbase size`t=`t"     me32.modBaseSize
    Sleep 200
  }

  ;//  Do not forget to clean up the snapshot object.
  DllCall("CloseHandle","PTR",hModuleSnap)
  return TRUE
}
; 				Enumerate a structure.
; enumerate simple structure
MyStruct:="a,b,c"
s:=Struct(MyStruct,,{a:1,b:2,c:3})
for k, v in s
  MsgBox k ": " v
; ENUMERATE ARRAY OF STRUCTURES
MyStruct:="a,b,c"
s:=Struct("MyStruct[3]",,[{a:1,b:2,c:3},{a:4,b:5,c:6},{a:7,b:8,c:9}])
for k, v in s
  for key,value in v
  	MsgBox key ": " value
; ENUMERATE DYNAMIC STRUCTURE
MyStruct:="a,b,c"
s:=Struct("Short size,LPTSTR name,MyStruct ms",,{size:sizeof(MyStruct),name:"MyStruct",ms:{a:1,b:2,c:3}})
for k, v in s
  if !IsObject(v)
  	MsgBox k ": " v
	else
  	for key,value in v
  		MsgBox key ": " value