Structures

A structure or struct is a collection of members (fields) stored adjacently in memory.

Structs are most often used with external functions via DllCall, or with Gui controls via SendMessage. In those cases, the memory offset of each field relative to the start of the struct must exactly match the offset expected by the external function or control, usually as defined in an SDK or documentation meant for C/C++ developers.

In AutoHotkey v2.0 and earlier, structs were typically "defined" by allocating memory with Buffer (or other means) and directly manipulating it with NumPut and NumGet. This requires calculating the offset of each individual field as needed, so is a laborious and error-prone process. Fields are generally not referenced by name, making the code hard to read and maintain.

Warning: This is preliminary documentation for v2.1. Specifics may change with each alpha release.

With v2.1, the script can define a struct as a sequence of typed properties. This allows the script to refer to fields by name, relying on automatic calculation of field offsets and struct sizes. Usually a struct class definition is used, but a struct class can also be constructed at runtime.

Using a Class Definition [v2.1-alpha.22+]

Struct classes are defined using struct instead of class, and default to extending Struct instead of Object.

Each field in a struct is defined by writing its name, followed by a colon, then a type specifier. This is also called a typed property. Multiple typed properties can be defined on one line by separating them with a comma. For example:

struct POINT {
    x : Int32, y : Int32
}

All fields are zero-initialized when the object is constructed, before initializers are evaluated. A field can be initialized to some other value by including an assignment after the type. For example:

struct LVINSERTMARK {
    cbSize      : UInt32 := this.Size
    dwFlags     : UInt32
    iItem       : Int32
    dwReserved  : UInt32
}

A struct class can be instantiated the normal way, by calling it:

im := LVINSERTMARK()

As structs often come from external sources, an address can be converted to a reference to a struct by calling the At method.

Fields defined by a subclass are always placed after fields defined by the base class. For example, when class StructB extends StructA, the layout is the same as if StructB extends Object but begins with a nested StructA. However, inherited properties are accessed directly, not through a nested struct.

#StructPack can be used to override the default alignment of fields within the struct.

All elements of a class definition are permitted, but an initializer such as x := 0 cannot create a new property.

Using DefineProp

A struct can be defined at runtime by creating a Class object and adding typed properties to its Prototype with DefineProp. For example:

POINT := Class(Struct)  ; v2.1-alpha.21 and older used Object instead of Struct.
DefineProp(POINT.Prototype, 'x', {type: Int32})
DefineProp(POINT.Prototype, 'y', {type: Int32})
pt := POINT()

Typically the class object (POINT) would be defined once and used to create many instances (pt).

Unlike declarations in a class definition, literal reserved type names such as "i32" must be quoted when calling DefineProp.

As with a class definition, fields can be inherited from the prototype's base.

[v2.1-alpha.19+]: Pack can be used to override the default alignment of fields within the struct. For example:

AB := Class(Struct)
DefineProp(AB.Prototype, 'a', {type: Int8})
DefineProp(AB.Prototype, 'b', {type: Int32, pack: 1})
MsgBox AB().Size

Type Specifiers [v2.1-alpha.30+]

A type specifier in a class definition can be:

Type specifiers are evaluated in sequence with static property initializers, when the class is initialized (not each time it is instantiated).

The type specifier must evaluate to a struct class with non-zero size. It is evaluated in the context of static __Init(); i.e. this refers to the class object.

A struct class is any subclass of Struct, including the numeric types, array classes, pointer classes and Struct.Ptr itself.

Numeric Types [v2.1-alpha.23+]

The following struct classes are predefined for numeric types:

Note: AutoHotkey does not support 64-bit unsigned integers, but signed integers can generally be used instead.

As these are struct classes, each one has a pointer class and array classes. For example, UInt32.Ptr is equivalent to "UInt*" when used with DllCall, and UInt8[64] is the class of an array of 64 bytes.

When used as a property type, DllCall parameter type or array element type, conversion is handled directly and the class is not instantiated. For DllCall, this includes the pointer classes. For instance, a return value of type Int32.Ptr is exactly equivalent to "int*" and always dereferences the pointer (returns the integer value it points to) rather than returning a boxed pointer.

An instance of a numeric struct class acts not as a number, but as a virtual reference to a numeric variable. As with struct instances, we call it a boxed pointer if it encapsulates an unsafe pointer, or a boxed struct if its lifecycle is managed by the script. An instance can be created as follows:

Each instance has a property named __Value of the type represented by the class. This is typically invoked by the dereference operator or when using the instance as a virtual reference. For example, MouseGetPos(Int32.at(pt.ptr)) would store the X coordinate at the beginning of the struct pt.

Untyped Binary Data

If a property's type specifier is an integer, it is interpreted as the size of the property, in bytes. When the property is evaluated, the address of the property itself is returned, rather than any value contained by the property. This is generally only useful when defining an abstract type, such as a fixed-size string or array.

Nested Structs

Nested structs are constructed in order of definition, prior to initialization of the outer struct. The nested struct's own field initializers are evaluated and __New is called (without parameters) before before any initializers in the outer struct.

Note: Due to their specific needs for memory allocation and initialization, nested structs are constructed directly, not by calling the static Call method of the class.

When the outer struct is about to be deleted, __Delete is called for the outer struct first, followed by each nested struct (if defined) in reverse order of definition. The memory of the outer struct is deleted only after all destructors are called.

By default, the property corresponding to a nested struct returns a reference to the nested struct, and cannot be assigned a value. This can be overridden by defining a getter and/or setter for the __Value property. If a setter is defined and no getter, the struct itself is still returned by default.

Abstract Types

A struct can be used to implement a variety of abstract types, such as smart pointers, arrays, enums, specialized string types, or types that are restricted to specific values. For example, this implements a bool type like in C++:

struct bool {
    value : UInt8  ; This property name has no special meaning.
    __value {
        get => this.value
        set => this.value := !!value
    }
}

struct Example {
    bInformed : bool
    __new() {
        for value in [false, "truthy", 3] {
            this.bInformed := value
            MsgBox value " converts to " this.bInformed
        }
    }
}

Example()

The __Value property is also used by DllCall. For example, when the bool class is used as a DllCall parameter type, any incoming parameter value is automatically converted to 0 or 1 by !!value.

There are a variety of conventions for strings in structs and DllCall. Currently typed properties have no built-in support for strings or arrays, but they can be implemented by scripts using these mechanisms. The CString and BSTR examples demonstrate this.

Pointers to Structs [v2.1-alpha.22+]

Each struct class has a static property named Ptr which returns a pointer class. A pointer class is generally not instantiated, but is used with DllCall and typed properties to specify a pointer-to-struct parameter or field. For example, given an appropriate definition of the RECT struct class, RECT.Ptr represents the C type RECT* or LPRECT.

Boxed Pointer

Rather than encapsulating pointer values as their own unique type, they are treated as instances of the target struct class, with all of the same properties and type identity. To differentiate, we can use the term "boxed struct" to refer to an instance constructed by the script, and "boxed pointer" to refer to an instance which encapsulates a pointer value.

An address (pointer value) can be boxed by calling the At method.

Ref := StructClass.At(Address)

Since Ref is treated as an instance of StructClass, Ref is StructClass && Ref.Ptr = Address is true and Ref.Size returns the size of the struct (defined by the class). This means that if Ref is passed to a DllCall Ptr parameter, NumGet or NumPut, the parameter value or target address is the address of the struct, not the address at which the pointer is stored.

The lifetime of the actual struct is independent from the lifetime of a boxed pointer - the "box" only contains the pointer, not the struct. In many cases the struct is constructed by external code; the method of allocation can vary and may be unknowable. The script must ensure that no typed properties are queried after the struct is deleted.

Warning: The debugger may query properties automatically, so it is best to always release any boxed pointers before the target struct is deleted.

The struct is expected to have already been constructed (typically by external code), so __Init and __New are not called by At.

Any number of boxed pointers can exist for a given address, and can even have different classes. Different instances do not compare equal; the "boxes" are compared, not the pointer value.

DllCall

When used as a DllCall parameter type, StructClass.Ptr is handled similarly to any type with the * suffix. That is, the parameter can accept a VarRef, a value of the base type, or a value which can be converted to the base type. If a VarRef is passed, the value is read back after the call and assigned to the VarRef, potentially converting it in the process. The specifics are as follows:

When used as a return type, the return value is a boxed pointer, or unset if null.

Pointers in Structs

A pointer class can be used as the type of a struct field or typed property.

For both assignment and return, unset is used to represent a null pointer (0).

When non-null, the high-level value of the property is an instance of the target class (either a boxed pointer or a boxed struct).

If a boxed struct of an appropriate type is assigned to the property, a reference is retained to ensure it is not deleted until after the script releases its reference to the outer struct or boxed pointer. For example, it is typically safe to use outer.lpEx := Example(), whereas myVar := Example().Ptr would free the new struct immediately after the assignment. If the underlying pointer value has been changed, evaluating outer.lpEx causes the held reference to be released.

If a pointer class is constructed directly, its __Value property behaves as described above.

Structured Arrays [v2.1-alpha.23+]

Structured arrays have a fixed size and all elements must be the same type. Both the array length and element type are defined by the class, which is a subclass of Struct.Array. The subclass is not created by defining it in the script, but instead by using the static __Item property of a Struct subclass. For example:

struct X {
    y : Int32[10]
}
z := X()
MsgBox z.Size ; 40
MsgBox z.y.Size ; 40
MsgBox z.y.Length ; 10
MsgBox z.y is Int32[10] ; true
MsgBox z.y is Int32[100] ; false

struct XX {
    items : X[2]
}
zz := XX()
MsgBox zz.Size ; 80

Examples

Shows the class name of the window/control under the mouse cursor.

struct Point {
    x : Int32, y : Int32
}

; Use the class itself to pass by value (C type: POINT).
WindowFromPoint := DllCall.Bind("WindowFromPoint", Point, unset, "uptr")

; Use "ptr" to pass by reference without type-checking (C type: POINT*).
GetCursorPos := DllCall.Bind("GetCursorPos", "ptr", unset)
pt1 := Point()
GetCursorPos(pt1)
MsgBox WinGetClass(WindowFromPoint(pt1))

; Use .Ptr to pass by reference or receive output via a VarRef.
GetCursorPos := DllCall.Bind("GetCursorPos", Point.Ptr, unset)
GetCursorPos(&pt2)
MsgBox WinGetClass(WindowFromPoint(pt2))

Retrieves information about the monitor containing the mouse cursor, using nested structs.

struct RECT {
    Left : Int32
    Top : Int32
    Right : Int32
    Bottom : Int32
}

struct MONITORINFO {
    cbSize : Int32 := 40
    rcMonitor : RECT
    rcWork : RECT
    dwFlags : Int32
}

DllCall("GetCursorPos", "uint64*", &point:=0)
hMonitor := DllCall("MonitorFromPoint", "int64", point, "uint", 0, "ptr")
DllCall("user32\GetMonitorInfo", "ptr", hMonitor, "ptr", MI := MONITORINFO())
MsgBox(
(
    "Monitor info from point:
    Left: " MI.rcMonitor.Left "
    Top: " MI.rcMonitor.Top "
    Right: " MI.rcMonitor.Right "
    Bottom: " MI.rcMonitor.Bottom "
    WALeft: " MI.rcWork.Left "
    WATop: " MI.rcWork.Top "
    WARight: " MI.rcWork.Right "
    WABottom: " MI.rcWork.Bottom "
    Primary: " MI.dwFlags
))

Defines a struct containing a fixed-size string with a specific encoding.

; Define a reusable "meta-class" or "generic class" for C-style strings.
; Calling CString itself returns a new class for use in a type expression.
CString(n, cp:="UTF-16") {
    c := Class(Struct)
    unitSize := StrPut("", cp)  ; Size in bytes of a single "character".
    totalSize := n * unitSize  ; Total size of any field of this type.
    DefineProp(c.Prototype, 'Ptr', {type: totalSize})  ; See Untyped Binary Data.
    DefineProp(c.Prototype, 'Size', {value: totalSize})
    DefineProp(c.Prototype, '__value', {
        get: (this)        => StrGet(this,, cp),
        set: (this, value) => StrPut(value, this,, cp)
    })
    return c
}

; Define a struct containing a string of at most 32 UTF-8 code units.
struct XStruct {
  str : CString(32, "UTF-8")
}

x := XStruct()
x.str := "Hello!"
MsgBox x.str

Defines a struct which handles conversion of strings to/from BSTR.

MsgBox DllCall("oleaut32\SysStringLen", BSTR, "abc" Chr(0) "123")
MsgBox DllCall("oleaut32\SysAllocString", "wstr", "xyz", BSTR)

struct BSTR {  ; This type can also be used in a struct.
    ptr : IntPtr
    size => DllCall("oleaut32\SysStringByteLen", "ptr", this, "uint")
    __value {
        get => StrGet(this)
        set {
            if this.ptr  ; In case of use in a struct.
                this.__delete()
            this.ptr := DllCall("oleaut32\SysAllocStringLen", "wstr", value, "uint", StrLen(value), "ptr")
        }
    }
    __delete() => DllCall("oleaut32\SysFreeString", "ptr", this)
}

Calls a function that returns a struct by value.

struct lldiv_t {
    quot : Int64, rem : Int64
}
res := DllCall("ucrtbase\lldiv", "int64", 31558149, "int64", 3600, lldiv_t)
MsgBox Format("Earth orbit: {} hours and {} seconds.", res.quot, res.rem)

Defines a return type struct for error-checking and automatic cleanup.

struct LoadedHModule {
    h : IntPtr
    __value => this.h ? this : throw(OSError(A_LastError, -1))
    __delete() => DllCall("FreeLibrary", "ptr", this.h)
}

check
gdip := DllCall("LoadLibrary", "str", "gdiplus", LoadedHModule)
check
gdip := unset
check

DllCall("LoadLibrary", "str", "this is bound to fail", LoadedHModule) ; OSError(126)

; By contrast with LoadLibrary, a zero return value from GetModuleHandle
; is expected, and a non-zero handle should not be freed automatically.
check() => MsgBox(DllCall("GetModuleHandle", "str", "gdiplus", "ptr") ? "Loaded" : "Not loaded")