Objects

Note: This page is a work in progress.

An object combines a number of properties and methods.

Related topics:

IsObject can be used to determine if a value is an object:

Result := IsObject(expression)

See Object Types in the documentation side bar for a list of standard object types. There are two fundamental types:

Table of Contents

Basic Usage

Arrays

Create an Array:

MyArray := [Item1, Item2, ..., ItemN]
MyArray := Array(Item1, Item2, ..., ItemN)

Retrieve an item (or array element):

Value := MyArray[Index]

Change the value of an item (must already exist in the array):

MyArray[Index] := Value

Insert one or more items at a given index using the InsertAt method:

MyArray.InsertAt(Index, Value, Value2, ...)

Append one or more items using the Push method:

MyArray.Push(Value, Value2, ...)

Remove an item using the RemoveAt method:

RemovedValue := MyArray.RemoveAt(Index)

Remove the last item using the Pop method:

RemovedValue := MyArray.Pop()

Length returns the number of items in the array. Looping through an array's contents can be done either by index or with a For-loop. For example:

MyArray := ["one", "two", "three"]

; Iterate from 1 to the end of the array:
Loop MyArray.Length
    MsgBox MyArray[A_Index]

; Enumerate the array's contents:
For index, value in MyArray
    MsgBox "Item " index " is '" value "'"
    
; Same thing again:
For value in MyArray
    MsgBox "Item " A_Index " is '" value "'"

Maps (Associative Arrays)

A Map or associative array is an object which contains a collection of unique keys and a collection of values, where each key is associated with one value. Keys can be strings, integers or objects, while values can be of any type. An associative array can be created as follows:

MyMap := Map("KeyA", ValueA, "KeyB", ValueB, ..., "KeyZ", ValueZ)

Retrieve an item, where Key is a variable or expression:

Value := MyMap[Key]

Assign an item:

MyMap[Key] := Value

Remove an item using the Delete method:

RemovedValue := MyMap.Delete(Key)

Enumerating items:

MyMap := Map("ten", 10, "twenty", 20, "thirty", 30)
For key, value in MyMap
    MsgBox key ' = ' value

Objects

An object can have properties, methods and items (such as array elements). Items are accessed using [] as shown in the previous sections. Properties and methods are usually accessed by writing a dot followed by an identifier (just a name).

Examples:

Retrieve or set a property literally named Property:

Value := Object.Property
Object.Property := Value

Retrieve or set a property where the name is determined by evaluating an expression or variable:

Value := Object.%Expression%
Object.%Expression% := Value

Call a method literally named Method:

ReturnValue := Object.Method(Parameters)

Call a method where the name is determined by evaluating an expression or variable:

ReturnValue := Object.%Expression%(Parameters)

Some properties can accept parameters:

Value := Object.Property[Parameters]
Object.Property[Parameters] := Value

In fact, the array indexing syntax MyArray[Index] actually invokes the __Item property of MyArray, passing Index as a parameter.

Freeing Objects

Scripts do not free objects explicitly. When the last reference to an object is released, the object is freed automatically. A reference stored in a variable is released automatically when that variable is assigned some other value. For example:

obj := {}  ; Creates an object.
obj := ""  ; Releases the last reference, and therefore frees the object.

Similarly, a reference stored in a property or array element is released when that property or array element is assigned some other value or removed from the object.

arr := [{}]  ; Creates an array containing an object.
arr[1] := {}  ; Creates a second object, implicitly freeing the first object.
arr.RemoveAt(1)  ; Removes and frees the second object.

Because all references to an object must be released before the object can be freed, objects containing circular references aren't freed automatically. For instance, if x.child refers to y and y.parent refers to x, clearing x and y is not sufficient since the parent object still contains a reference to the child and vice versa. To resolve this situation, remove the circular reference.

x := {}, y := {}             ; Create two objects.
x.child := y, y.parent := x  ; Create a circular reference.

y.parent := ""               ; The circular reference must be removed before the objects can be freed.
x := "", y := ""             ; Without the above line, this would not free the objects.

For more advanced usage and details, see Reference Counting.

Extended Usage

Function References

If the variable func contains a function name, the function can be called with the expression %func%(). However, this requires the function name to be resolved each time, which is inefficient if the function is called more than once. To improve performance, the script can retrieve a reference to the function and store it for later use:

MyFuncRef := Func("MyFunc")

A function can be called by reference using the following syntax:

RetVal := %MyFuncRef%(Params)
RetVal := MyFuncRef.Call(Params)

For details about additional properties of function references, see Func Object.

In most cases, any callable object can be used in place of a function reference.

Arrays of Arrays

Although "multi-dimensional" arrays are not supported, a script can combine multiple arrays or maps. For example:

grid := [[1,2,3],
         [4,5,6],
         [7,8,9]]
MsgBox grid[1][3] ; 3
MsgBox grid[3][2] ; 8

A custom object can implement multi-dimensional support by defining an __Item property. For example:

class Array2D extends Array {
    __new(x, y) {
        this.Length := x * y
        this.Width := x
        this.Height := y
    }
    __Item[x, y] {
        get => base[this.Width * (y-1) + x]
        set => base[this.Width * (y-1) + x] := value
    }
}

grid := Array2D.new(3, 4)
grid[1, 4] := "#"
grid[2, 3] := "#"
grid[2, 2] := "#"
grid[3, 1] := "#"
gridtext := ""
Loop grid.Width {
    x := A_Index
    Loop grid.Height {
        y := A_Index
        gridtext .= grid[x, y] || "-"
    }
    gridtext .= "`n"
}
MsgBox gridtext

A real script should perform error-checking and override other methods, such as __Enum to support enumeration.

Custom Objects

There are two general ways to create custom objects:

Meta-functions can be used to further control how an object behaves.

Note: Within this section, an object is any instance of the Object class. This section does not apply to COM objects.

Ad Hoc

Properties and methods can generally be added to new objects at any time. For example, an object with one property and one method might be constructed like this:

; Create an object.
thing := {}
; Store a value.
thing.foo := "bar"
; Define a method.
thing.DefineMethod "test", Func("thing_test")
; Call the method.
thing.test()

thing_test(this) {
    MsgBox this.foo
}

When thing.test() is called, thing is automatically inserted at the beginning of the parameter list. By convention, the function is named by combining the "type" of object and the method name, but this is not a requirement.

See also: DefineMethod, DefineProp

Delegation

Objects are prototype-based. That is, any properties or methods not defined in the object itself can instead be defined in the object's base. This is known as inheritance by delegation or differential inheritance, because an object can implement only the parts that make it different, while delegating the rest to its base.

Although a base object is also generally known as a prototype, we use "a class's Prototype" to mean the object upon which every instance of the class is based, and "base" to mean the object upon which an instance is based (or sometimes the base class/superclass of the current class).

AutoHotkey's object design was influenced primarily by JavaScript and Lua, with a little C#. We use obj.base in place of JavaScript's obj.__proto__ and cls.Prototype in place of JavaScript's func.prototype. (Class objects are used in place of constructor functions.)

An object's base is also used to identify its type or class. For example, x := [] creates an object based on Array.Prototype, which means that the expressions x is Array and x.HasBase(Array.Prototype) are true, and type(x) returns "Array".

Any instance of Object or a derived class can be a base object, but an object can only be assigned as the base of an object with the same native type. This is to ensure that built-in methods can always identify the native type of an object, and operate only on objects that have the correct binary structure.

Base objects can be defined two different ways:

A base object can be assigned to the base property of another object, but typically an object's base is set implicitly when it is created.

Creating a Base Object

Any object can be used as the base of any other object which has the same native type. The following example builds on the previous example under Ad Hoc (combine the two before running it):

other := {}
other.base := thing
other.test()

In this case, other inherits foo and test from thing. This inheritance is dynamic, so if thing.foo is modified, the change will be reflected by other.foo. If the script assigns to other.foo, the value is stored in other and any further changes to thing.foo will have no effect on other.foo. When other.test() is called, its this parameter contains a reference to other instead of thing.

Classes

In object-oriented programming, a class is an extensible program-code-template for creating objects, providing initial values for state (member variables) and implementations of behavior (member functions or methods). Wikipedia

In more general terms, a class is a set or category of things having some property or attribute in common. In AutoHotkey, a class defines properties and methods to be shared by instances of the class. An instance is just an object which inherits properties and methods from the class, and can typically also be identified as belonging to that class (such as with the expression instance is ClassName). Instances are typically created by calling ClassName.new().

Since Objects are dynamic and prototype-based, each class consists of two parts:

The following shows most of the elements of a class definition:

class ClassName extends BaseClassName
{
    InstanceVar := Expression
    
    static ClassVar := Expression

    class NestedClass
    {
        ...
    }

    Method()
    {
        ...
    }
    
    static Method()
    {
        ...
    }

    Property[]  ; Brackets are optional
    {
        get {
            return value of property
        }
        set {
            Store or otherwise handle value
        }
    }
    
    ShortProperty[]
    {
        get => Expression which calculates property value
        set => Expression which stores or otherwise handles value
    }
    
    ShorterProperty[] => Expression which calculates property value
}

When the script is loaded, this constructs a Class object and stores it in the super-global variable ClassName. To reference this class inside a force-local function, a declaration such as global ClassName is required. If extends BaseClassName is present, BaseClassName must be the full name of another class. The full name of each class is stored in ClassName.Prototype.__Class.

Because the class is referenced via a variable, the class name cannot be used to both reference the class and create a separate variable (such as to hold an instance of the class) in the same context. For example, box := new Box would replace the class object in Box with an instance of itself. #Warn ClassOverwrite enables a warning to be shown at load time for each attempt to overwrite a class.

Within this documentation, the word "class" on its own usually means a class object constructed with the class keyword.

Class definitions can contain variable declarations, method definitions and nested class definitions.

Instance Variables

An instance variable is one that each instance of the class has its own copy of. They are declared like normal assignments, but the this. prefix is omitted (only directly within the class body):

InstanceVar := Expression

These declarations are evaluated each time a new instance of the class is created with the new keyword or ClassName.new(), after all base-class declarations are evaluated but before __New is called. This is achieved by automatically creating a method named __Init containing a call to base.__Init() and inserting each declaration into it. Therefore, a single class definition must not contain both an __Init method and an instance variable declaration.

Expression can access other instance variables and methods via this, but all other variable references are assumed to be global.

To access an instance variable (even within a method), always specify the target object; for example, this.InstanceVar.

Declarations like x.y := z are also supported, provided that x was previously declared in this class. For example, x := {}, x.y := 42 declares x and also initializes this.x.y.

Static/Class Variables

Static/class variables belong to the class itself, but their values can be inherited by subclasses. They are declared like instance variables, but using the static keyword:

static ClassVar := Expression

These declarations are evaluated only once, when the class is initialized. In the current release, classes are initialized in the order that they appear in the script, before evaluation of the auto-execute section and before any static declarations contained by functions. A static method named __Init is automatically defined for this purpose.

Each declaration stores a value in the class object. Any variable references in Expression are assumed to be global.

To assign to a class variable, always specify the class object; for example, ClassName.ClassVar := Value. If a subclass does not own a property by that name, Subclass.ClassVar can also be used to retrieve the value; so if the value is a reference to an object, subclasses will share that object by default. However, Subclass.ClassVar := y would store the value in Subclass, not in ClassName.

Declarations like static x.y := z are also supported, provided that x was previously declared in this class. For example, static x := {}, x.y := 42 declares x and also initializes ClassName.x.y.

Nested Classes

Nested class definitions allow a class object to be associated with a static/class variable of the outer class instead of a separate global variable. In the example above, class NestedClass constructs a Class object and stores it in ClassName.NestedClass. Subclasses could inherit NestedClass or override it with their own nested class (in which case WhichClass.NestedClass.new() could be used to instantiate whichever class is appropriate).

class NestedClass
{
    ...
}

Nesting a class does not imply any particular relationship to the outer class. The nested class is not instantiated automatically, nor do instances of the nested class have any connection with an instance of the outer class, unless the script explicitly makes that connection.

Methods

Method definitions look identical to function definitions. Each method has a hidden parameter named this, which contains a reference to the object on which the method was called. There are two types of methods:

Method()
{
    ...
}

Inside a method, the pseudo-keyword base can be used to access the superclass versions of methods or properties which are overridden in a derived class. For example, base.Method() in the class defined above would call the version of Method which is defined by BaseClassName. Meta-functions are not called unless they are explicitly named. Note:

base only has special meaning if followed by a dot . or brackets [], so code like obj := base, obj.Method() will not work. Scripts can disable the special behaviour of base by assigning it a non-empty value; however, this is not recommended.

Fat arrow syntax can be used to define a single-line method which returns an expression:

Method() => Expression

See also: DefineMethod

Properties

A property definition creates a dynamic property, which calls a method instead of simply storing or returning a value.

Property[]  ; Brackets are optional
{
    get {
        return property value
    }
    set {
        Store or otherwise handle value
    }
}

Property is simply the name of the property, which will be used to invoke it. For example, obj.Property would call get while obj.Property := value would call set. Within get or set, this refers to the object being invoked. Within set, value contains the value being assigned.

Parameters can be defined by enclosing them in square brackets to the right of the property name, and are passed the same way. Aside from using square brackets, parameters of properties are defined the same way as parameters of methods - optional, ByRef and variadic parameters are supported.

If a property cannot accept parameters (the square brackets are omitted or empty), parameters are automatically forwarded to the __Item property of the object returned by get. For example, this.Property[x] would have the same effect as (this.Property)[x] or y := this.Property, y[x].

Static properties can be defined by preceding the property name with the separate keyword static. In that case, this refers to the class itself or a subclass.

The return value of set is ignored. For example, val := obj.Property := 42 always assigns val := 42 regardless of what the property does, unless it throws an exception or exits the thread.

Each class can define one or both halves of a property. If a class overrides a property, it can use base.Property to access the property defined by its base class. If Get or Set is not defined, it can be inherited from a base object. If Get is undefined, the property can return a value inherited from a base. If Set is undefined in this and all base objects, the property is read-only (attempting to set the property throws an exception).

Internally, get and set are two separate methods, so cannot share variables (except by storing them in this).

See also: DefineProp

Meta-functions provide a broader way of controlling access to properties and methods of an object, but are more complicated and error-prone.

Fat Arrow Properties

Fat arrow syntax can be used to define a property getter or setter which returns an expression:

ShortProperty[]
{
    get => Expression which calculates property value
    set => Expression which stores or otherwise handles value
}

When defining only a getter, the braces and get can be omitted:

ShorterProperty[] => Expression which calculates property value

__Enum Method

__Enum(NumberOfVars)

The __Enum method is called when the object is passed to a for-loop. This method should return an enumerator which will return items contained by the object, such as array elements. If left undefined, the object cannot be passed directly to a for-loop.

NumberOfVars contains the number of variables passed to the for-loop. If NumberOfVars is 2, the enumerator is expected to assign the key or index of an item to the first parameter and the value to the second parameter. Each key or index should be accepted as a parameter of the __Item property. This enables DBGp-based debuggers to get or set a specific item after listing them by invoking the enumerator.

__Item Property

The __Item property is invoked when the indexing operator (array syntax) is used with the object. For example:

class Env {
    __Item[name] {
        get => EnvGet(name)
        set => EnvSet(name, value)
    }
}

Env["PATH"] .= ";" A_ScriptDir  ; Only affects this script and child processes.
MsgBox Env["PATH"]

__Item is effectively a default property name (if such a property has been defined):

For example:

obj := {}
obj[] := Map()     ; Equivalent to obj.__Item := Map()
obj["base"] := 10
MsgBox obj.base = Object.prototype  ; True
MsgBox obj["base"]                  ; 10

Note: When an explicit property name is combined with empty brackets, as in obj.prop[], it is handled as two separate operations: first retrieve obj.prop, then invoke the default property of the result. This is part of the language syntax, so is not dependent on the object.

Construction and Destruction

Whenever an object is created by ClassName.new() or the new keyword, its __New method is called in order to allow custom initialization. Any parameters passed to new are forwarded to __New, so can affect the object's initial content or how it is constructed. When an object is destroyed, __Delete is called. For example:

m1 := GMem.new(0, 10)
m2 := new GMem(0, 20)
m3 := {base: GMem.Prototype}.__New(0, 30)

; Note: For general memory allocations, use BufferAlloc() instead.
class GMem
{
    __New(aFlags, aSize)
    {
        this.ptr := DllCall("GlobalAlloc", "UInt", aFlags, "Ptr", aSize, "Ptr")
        if !this.ptr
            throw Exception("Out of memory")
        MsgBox "New GMem of " aSize " bytes at address " this.ptr "."
        return this  ; This line is only required if __new is called directly.
    }

    __Delete()
    {
        MsgBox "Delete GMem at address " this.ptr "."
        DllCall("GlobalFree", "Ptr", this.ptr)
    }
}

__Delete is not called for any object which owns a property named "__Class". Prototype objects have this property by default.

If an exception or runtime error is thrown while __Delete is executing and is not handled within __Delete, it acts as though __Delete was called from a new thread. That is, an error dialog is displayed and __Delete returns, but the thread does not exit (unless it was already exiting).

Each class may also have a static __New method, which is called immediately after its static variables are initialized (this occurs in the order that classes are defined in the script). This method can be inherited from a base class, and can therefore be used to initialize subclasses. Within static __New, this refers to either the class which defined the method, or a subclass.

Meta-Functions

class ClassName {
    __Get(Name, Params)
    __Set(Name, Params, Value)
    __Call(Name, Params)
}
Name

The name of the property or method.

Params

An Array of parameters. This includes only the parameters between () or [], so may be empty. The meta-function is expected to handle cases such as x.y[z] where x.y is undefined.

Value

The value being assigned.

Meta-functions define what happens when an undefined property or method is invoked. For example, if obj.unk has not been assigned a value, it invokes the __Get meta-function. Similarly, obj.unk := value invokes __Set and obj.unk() invokes __Call.

Properties and methods can be defined in the object itself or any of its base objects. In general, for a meta-function to be called for every property, one must avoid defining any properties. Built-in properties such as Base can be overridden with a property definition or DefineProp.

If a meta-function is defined, it must perform whatever default action is required. For example, the following might be expected:

Any callable object can be used as a meta-function by passing it to DefineMethod.

Meta-functions are never called when the property or method name is omitted:

Dynamic Properties

Property syntax and DefineProp can be used to define properties which compute a value each time they are evaluated, but each property must be defined in advance. By contrast, __Get and __Set can be used to implement properties which are known only at the moment they are invoked.

For example, a "proxy" object could be created which sends requests for properties over the network (or through some other channel). A remote server would send back a response containing the value of the property, and the proxy would return the value to its caller. Even if the name of each property was known in advance, it would not be logical to define each property individually in the proxy class since every property does the same thing (send a network request). Meta-functions receive the property name as a parameter, so are a good solution to this problem.

Default Base Object

Note: This section is out of date, and the default base object will be soon replaced with more specific classes.

When a non-object value is used with object syntax, the default base object is invoked. This can be used for debugging or to globally define object-like behaviour for strings, numbers and/or variables. The default base may be accessed by using .base with any non-object value; for instance, "".base. Although the default base cannot be set as in "".base := Object(), the default base may itself have a base as in "".base.base := Object().

Automatic Var Init

When an empty variable is used as the target of a set operation, it is passed directly to the __Set meta-function, giving it opportunity to insert a new object into the variable. For brevity, this example does not support multiple parameters; it could, by using a variadic function.

"".base.__Set := Func("Default_Set_AutomaticVarInit")

empty_var.foo := "bar"
MsgBox empty_var.foo

Default_Set_AutomaticVarInit(ByRef var, key, value)
{
    if (var = "")
        var := Object(key, value)
    return var
}

Pseudo-Properties

Object "syntax sugar" can be applied to strings and numbers.

"".base.__Get := Func("Default_Get_PseudoProperty")
"".base.is    := Func("Default_is")

MsgBox A_AhkPath.length " == " StrLen(A_AhkPath)
MsgBox A_AhkPath.length.is("int")

Default_Get_PseudoProperty(nonobj, key)
{
    if (key = "length")
        return StrLen(nonobj)
}

Default_is(nonobj, type)
{
    static alias := {int: "integer"}
    return nonobj is (alias[type] or type)
}

Note that built-in functions may also be used, but in this case the parentheses cannot be omitted:

"".base.length := Func("StrLen")
MsgBox A_AhkPath.length() " == " StrLen(A_AhkPath)

Debugging

By default, invoking a non-object value causes an exception to be thrown. The following example changes the behaviour so that a warning is shown and the script continues:

"".base.__Get := "".base.__Set := "".base.__Call := Func("Default__Warn")

empty_var.foo := "bar"
x := (1 + 1).is("integer")

Default__Warn(nonobj, p1:="", p2:="", p3:="", p4:="")
{
    ListLines
    MsgBox "A non-object value was improperly invoked.`n`nSpecifically: " nonobj
    return "" ; Override the default behaviour by explicitly returning.
}

Implementation

Reference-Counting

AutoHotkey uses a basic reference-counting mechanism to automatically free the resources used by an object when it is no longer referenced by the script. Script authors should not invoke this mechanism explicitly, except when dealing directly with unmanaged pointers to objects.

Temporary references returned by functions, methods or operators within an expression are released after evaluation of that expression has completed or been aborted. This allows temporary objects to be used for resource management. For example:

MsgBox DllCall("GlobalSize", "ptr", (new GMem(0, 20)), "ptr")  ; 20

To run code when the last reference to an object is being released, implement the __Delete meta-function.

Known Limitations:

Although memory used by the object is reclaimed by the operating system when the program exits, __Delete will not be called unless all references to the object are freed. This can be important if it frees other resources which are not automatically reclaimed by the operating system, such as temporary files.

Pointers to Objects

In some rare cases it may be necessary to pass an object to external code via DllCall or store it in a binary data structure for later retrieval. An object's address can be retrieved via address := &object; however, this effectively makes two references to the object, but the program only knows about the one in object. If the last known reference to the object was released, the object would be deleted. Therefore, the script must inform the object that it has gained a reference. This can be done as follows:

ObjAddRef(address := &object)

The script must also inform the object when it is finished with that reference:

ObjRelease(address)

Generally each new copy of an object's address should be treated as another reference to the object, so the script should call ObjAddRef when it gains a copy and ObjRelease immediately before losing one. For example, whenever an address is copied via something like x := address, ObjAddRef should be called. Similarly, when the script is finished with x (or is about to overwrite x's value), it should call ObjRelease.

To convert an address to a proper reference, use the Object function:

MyObject := Object(address)

Note that the Object function can be used even on objects which it did not create, such as COM objects and File objects.