Objekte

Ein Objekt ist in AutoHotkey ein abstrakter Datentyp, das drei Grundfunktionen bereitstellt:

Ein Objektverweis ist ein Pointer oder Handle, der auf ein bestimmtes Objekt verweist. Objektverweise können, wie Zeichenketten und Zahlen auch, in Variablen gespeichert werden, an Funktionen übergeben oder von Funktionen zurückgegeben werden und in Objekten gespeichert werden. Hat man einen Verweis von einer Variable in einer anderen gespeichert (z. B. mit x := y), würden beide Variablen auf das gleiche Objekt verweisen.

Mit IsObject kann festgestellt werden, ob ein Wert ein Objekt ist:

Ergebnis := IsObject(Ausdruck)

Es gibt folgende Objekttypen:

Inhaltsverzeichnis

Grundlagen

Einfache Arrays

Erstellen eines Arrays:

Array := [Element1, Element2, ..., ElementN]
Array := Array(Element1, Element2, ..., ElementN)

Abrufen eines Elements:

Wert := Array[Index]

Zuweisen eines Elements:

Array[Index] := Wert

Einfügen beliebig vieler Elemente ab einem bestimmten Index:

Array.InsertAt(Index, Wert, Wert2, ...)

Anfügen eines oder von mehreren Elementen:

Array.Push(Wert, Wert2, ...)

Entfernen eines Elements:

EntfernterWert := Array.RemoveAt(Index)

Entfernen des letzten Elements:

EntfernterWert := Array.Pop()

Sofern Array nicht leer ist, können MinIndex und MaxIndex/Length den niedrigsten und höchsten Index des Arrays zurückgeben. Während der niedrigste Index fast immer 1 ist, wird MaxIndex üblicherweise die Anzahl der Elemente zurückgeben. Sind aber keine Integer-Keys vorhanden, gibt MaxIndex eine leere Zeichenkette zurück, während Length eine 0 zurückgibt. Um die Inhalte des Arrays zu durchlaufen, benutzt man entweder den Index oder eine For-Schleife. Zum Beispiel:

array := ["eins", "zwei", "drei"]

; Durchläuft das Array von 1 bis zu seinem Ende:
Loop % array.Length()
    MsgBox % array[A_Index]

; Enumeriert die Inhalte des Arrays:
For index, wert in array
    MsgBox % "Element " index " ist '" wert "'"

Assoziative Arrays

Ein assoziatives Array ist ein Objekt, das mehrere eindeutige Keys (Schlüssel) und mehrere Values (Werte) enthält, die jeweils miteinander verbunden sind. Keys können Zeichenketten, Integer oder Objekte sein. Values beliebige Typen. Ein assoziatives Array kann wie folgt erstellt werden:

Array := {KeyA: ValueA, KeyB: ValueB, ..., KeyZ: WertZ}
Array := Object("KeyA", ValueA, "KeyB", ValueB, ..., "KeyZ", ValueZ)

Wenn man die {Key:Value}-Schreibweise verwendet, können die Anführungszeichen beim Key weggelassen werden, sofern dieser nur aus Wortzeichen besteht. Jeder Ausdruck kann als Key verwendet werden. Um eine Variable als Key zu benutzen, muss es mit runden Klammern umschlossen werden. Zum Beispiel wäre sowohl {(KeyVar): Value} als auch {GetKey(): Value} eine gültige Angabe.

Abrufen eines Elements:

Value := Array[Key]

Zuweisen eines Elements:

Array[Key] := Value

Entfernen eines Elements:

EntfernterWert := Array.Delete(Key)

Enumerieren von Elementen:

array := {zehn: 10, zwanzig: 20, dreißig: 30}
For key, value in array
    MsgBox %key% = %value%

Assoziative Arrays können lückenhaft gefüllt werden - zum Beispiel enthält {1: "a", 1000: "b"} nur zwei Key-Value-Paare, nicht 1000.

In AutoHotkey v1.x sind einfache Arrays und assoziative Arrays das gleiche. Das Behandeln von [] als einfaches lineares Array stellt sicher, dass dessen Rolle eindeutig zugeordnet werden kann, und erhöht die Chance, dass dein Skript auch mit einer zukünftigen Version von AutoHotkey noch funktionieren wird, wo vielleicht zwischen einfachem und assoziativem Array unterschieden wird.

Objekte

Abrufen einer Eigenschaft:

Wert := Objekt.Eigenschaft

Setzen einer Eigenschaft:

Objekt.Eigenschaft := Wert

Aufrufen einer Methode:

Rückgabewert := Objekt.Methode(Parameter)

Aufrufen einer Methode mit einem errechneten Methodennamen:

Rückgabewert := Object[Methodenname](Parameter)

Einige Eigenschaften von COM-Objekten und benutzerdefinierten Objekten können Parameter akzeptieren:

Wert := Objekt.Eigenschaft[Parameter]
Objekt.Eigenschaft[Parameter] := Wert

Siehe auch: Objekt, File-Objekt, Func-Objekt, COM-Objekt

Bekannte Einschränkung:

Freigeben von Objekten

Skripte geben Objekte nicht explizit frei. Sobald der letzte Verweis auf ein Objekt freigegeben ist, wird das Objekt automatisch freigegeben. Ein Verweis, der in einer Variable gespeichert ist, wird automatisch freigegeben, wenn diese Variable irgendeinen anderen Wert zugewiesen bekommt. Zum Beispiel:

obj := {}  ; Erstellt ein Objekt.
obj := ""  ; Gibt den letzten Verweis frei, wodurch das Objekt freigegeben wird.

In diesem Zusammenhang wird ein Verweis, der in einem Feld eines anderen Objekts gespeichert ist, freigegeben, wenn dieses Feld irgendeinen anderen Wert zugewiesen bekommt oder vom Objekt entfernt wird. Dies gilt auch für Arrays, weil sie eigentlich Objekte sind.

arr := [{}]  ; Erstellt ein Array, das ein Objekt enthält.
arr[1] := {}  ; Erstellt ein zweites Objekt, wodurch das erste Objekt indirekt freigegeben wird.
arr.RemoveAt(1)  ; Entfernt und gibt das zweite Objekt frei.

Da alle Verweise auf ein Objekt freigegeben werden müssen, bevor das Objekt freigegeben werden kann, können Objekte mit Zirkelbezügen nicht automatisch freigegeben werden. Wenn beispielsweise x.child auf y verweist und y.parent auf x verweist, würde es nicht genügen, x und y zu leeren, weil das Parent-Objekt noch einen Verweis auf das Child-Objekt enthält, und umgekehrt. Um diese Situation in den Griff zu bekommen, muss der Zirkelbezug entfernt werden.

x := {}, y := {}             ; Erstellt zwei Objekte.
x.child := y, y.parent := x  ; Erstellt ein Zirkelbezug.

y.parent := ""               ; Der Zirkelbezug muss entfernt werden, bevor die Objekte freigegeben werden können.
x := "", y := ""             ; Ohne die obige Zeile würde dies hier die Objekte nicht freigeben.

Für mehr Details, siehe Referenzzählung.

Bemerkungen

Syntax

Alle Objektarten unterstützen sowohl die Array-Syntax (eckige Klammern) als auch die Objekt-Syntax (Punkte).

Darüber hinaus können Objektverweise auch in Ausdrücken verwendet werden:

Dort, wo ein Objekt nicht erwartet wird, wird das Objekt als leere Zeichenkette angesehen. Zum Beispiel würde MsgBox %objekt% eine leere MsgBox anzeigen und objekt + 1 eine leere Zeichenkette zurückgeben. Verlasse dich nicht auf dieses Verhalten, weil es noch geändert werden könnte.

Ein Zuweisungsoperator unmittelbar nach einem Methodenaufruf ist das gleiche wie, als würde man eine Eigenschaft mit Parametern setzen. Zum Beispiel bewirken beide Zeilen dasselbe:

obj.item(x) := y
obj.item[x] := y

Verbundzuweisungen wie x.y += 1 und --arr[1] werden unterstützt.

[v1.1.20+]: Parameter können beim Abrufen oder Setzen von Eigenschaften weggelassen werden. Zum Beispiel x[,2]. Skripte können dies nutzen, indem sie Standardwerte für Parameter in Eigenschaften und Meta-Funktionen definieren. Den Methodennamen kann man zudem komplett weglassen, wie z. B. in x[](a). Skripte können dies nutzen, indem sie einen Standardwert für den ersten Parameter der __Call-Meta-Funktion definieren, da er sonst nicht im Wert enthalten wäre. Beachte, dass sich dies von x.(a) unterscheidet, das das gleiche wie x[""](a) ist. Wenn man den Eigenschafts- oder Methodennamen beim Aufrufen eines COM-Objekts weglässt, wird sein "Standardelement" aufgerufen.

Keys

Es gibt einige Einschränkungen, welche Werte als Keys innerhalb von Objekten, die mit [], {} oder dem new-Operator erstellt wurden, verwendet werden können:

Erweiterte Grundlagen

Funktionsverweise [v1.1.00+]

Enthält die Variable funk einen Funktionsnamen, kann die Funktion über mehrere Wege aufgerufen werden: %funk%() oder funk.(). Allerdings muss dabei die Funktion jedes Mal neu aufgelöst werden. Sobald die Funktion mehr als einmal aufgerufen wird, würde die Effizienz darunter leiden. Zur Verbesserung der Performance speichert man den Funktionsverweis in eine Variable, um sie später wiederzuverwenden:

Funk := Func("MeineFunk")

Eine Funktion kann mithilfe der folgenden Syntax per Verweis aufgerufen werden:

RückWert := %Func%(Params)     ; Benötigt v1.1.07+
RückWert := Func.Call(Params)  ; Benötigt v1.1.19+
RückWert := Func.(Params)      ; Nicht empfohlen

Um mehr über zusätzliche Eigenschaften von Funktionsverweisen zu erfahren, siehe Func-Objekt.

Mehrdimensionale Arrays

AutoHotkey unterstützt "mehrdimensionale" Arrays - das sind Arrays innerhalb von anderen Arrays. Eine Tabelle beispielsweise könnte man als Array mit Reihen ansehen, bei dem jede Reihe ein Array mit Spalten ist. In diesem Fall kann der Inhalt der Spalte y von der Reihe x mit einer der folgenden Methoden gesetzt werden:

Tabelle[x][y] := Inhalt  ; A
Tabelle[x, y] := Inhalt  ; B

Wenn Tabelle[x] nicht vorhanden ist, unterscheiden sich A und B wie folgt:

Mehrdimensionale Zuweisungen wie Tabelle[a, b, c, d] := Wert werden wie folgt behandelt:

Dieses Verhalten gilt nur für skript-erstellte Objekte, nicht für spezielle Objekttypen wie COM-Objekte oder COM-Arrays.

Arrays mit Funktionen

Ein Array mit Funktionen ist im Prinzip ein Array, das Funktionsnamen oder Verweise enthält. Zum Beispiel:

array := [Func("ErsteFunk"), Func("ZweiteFunk")]

; Ruft jede Funktion auf und übergibt "foo" als Parameter:
Loop 2
    array[A_Index].Call("foo")

; Ruft jede Funktion auf und übergibt indirekt das Array selbst als Parameter:
Loop 2
    array[A_Index]()

ErsteFunk(Parameter) {
    MsgBox % A_ThisFunc ": " (IsObject(Parameter) ? "Objekt" : Parameter)
}
ZweiteFunk(Parameter) {
    MsgBox % A_ThisFunc ": " (IsObject(Parameter) ? "Objekt" : Parameter)
}

Für die Abwärtskompatibilität wird die zweite Form array nicht als Parameter übergeben, wenn array[A_Index] einen Funktionsnamen statt einen Funktionsverweis enthält. Wenn aber array[A_Index] von array.base[A_Index] geerbt wurde, wird array als Parameter übergeben.

Benutzerdefinierte Objekte

Objekte, die vom Skript erstellt wurden, müssen nicht zwingend eine vordefinierte Struktur haben. Stattdessen kann jedes Objekt Eigenschaften und Methoden von seinem base-Objekt (auch bekannt als "Prototyp" oder "Klasse") erben. Bei einem Objekt kann man jederzeit Eigenschaften und Methoden hinzufügen oder entfernen. Solche Änderungen haben auch Auswirkung auf alle abgeleiteten Objekte. Bei komplexeren oder spezielleren Situationen kann das base-Objekt das Standardverhalten überschreiben, indem es Meta-Funktionen definiert.

Base-Objekte sind ganz normale Objekte, die mit den folgenden zwei Möglichkeiten erstellt werden können:

class baseObject {
    static foo := "bar"
}
; OR
baseObject := {foo: "bar"}

Um ein Objekt zu erstellen, das von einem anderen Objekt abgeleitet wurde, können Skripte die base-Eigenschaft zuweisen oder das new-Schlüsselwort verwenden:

obj1 := Object(), obj1.base := baseObject
obj2 := {base: baseObjekt}
obj3 := new baseObjekt
MsgBox % obj1.foo " " obj2.foo " " obj3.foo

Es ist jederzeit möglich, einem Objekt ein neues base zuzuweisen, das alle Eigenschaften und Methoden ersetzen würde, die das Objekt geerbt hat.

Prototypen

Prototypen oder base-Objekte können konstruiert und manipuliert werden, wie jedes andere Objekt auch. Zum Beispiel könnte ein gewöhnliches Objekt mit einer Eigenschaft und einer Methode wie folgt konstruiert werden:

; Erstellt ein Objekt.
obj := {}
; Speichert einen Wert.
obj.foo := "bar"
; Erstellt eine Methode durch Speichern eines Funktionsverweises.
obj.test := Func("obj_test")
; Ruft die Methode auf.
obj.test()

obj_test(this) {
   MsgBox % this.foo
}

Ruft man obj.test() auf, wird obj automatisch an den Anfang der Parameterliste gesetzt. Für die Abwärtskompatibilität wird das aber nicht passieren, wenn eine Funktion direkt per Name (statt per Verweis) in das Objekt gespeichert wird (statt von einem base-Objekt geerbt zu werden). Üblicherweise benennt man die Funktion nach dem Objekttypen und dem Methodennamen.

Ein Objekt ist ein Prototyp oder base, wenn ein anderes Objekt davon abgeleitet wurde:

obj2 := {}
obj2.base := obj
obj2.test()

In diesem Fall würde obj2 das foo und test von obj erben. Diese Vererbung ist dynamisch - würde man obj.foo ändern, wäre die Änderung auch bei obj2.foo bemerkbar. Wenn das Skript obj2.foo einen Wert zuweist, wird dieser Wert in obj2 gespeichert; weitere Änderungen an obj.foo hätten keinen Einfluss auf obj2.foo. Ruft man obj2.test() auf, würde dessen this-Parameter einen Verweis auf obj2 statt auf obj enthalten.

Klassen [v1.1.00+]

Im Grunde ist eine "Klasse" eine Reihe oder Kategorie von Dingen, die einige Eigenschaften oder Attribute haben. Da ein Base- oder Prototyp-Objekt Eigenschaften und Verhaltensweisen für eine Reihe von Objekten definiert, kann man es auch als Klassenobjekt bezeichnen. Der Einfachheit halber können Base-Objekte wie folgt mithilfe des Schlüsselworts "class" definiert werden:

class Klassenname extends BaseKlassenname
{
    InstanzVar := Ausdruck
    static KlassenVar := Ausdruck

    class VerschachtelteKlasse
    {
        ...
    }

    Methode()
    {
        ...
    }

    Eigenschaft[]  ; Die eckigen Klammern sind optional
    {
        get {
            return ...
        }
        set {
            return ... := Wert
        }
    }
}

Beim Laden des Skripts würde mit diesem Beispiel ein Objekt konstruiert und in die globale (oder seit v1.1.05 in die superglobale) Variable Klassenname gespeichert werden. Vor v1.1.05 war es notwendig, eine Deklaration wie global Klassenname anzugeben, um diese Klasse innerhalb einer Funktion zu verweisen, sofern die Funktion nicht global-behandelnd war. Wenn extends BaseKlassenname vorhanden ist, muss der BaseKlassenname der vollständige Name einer anderen Klasse sein (seit v1.1.11 spielt die Reihenfolge, in der sie definiert werden, keine Rolle mehr). Der vollständige Name jeder Klasse ist in objekt.__Class gespeichert.

Innerhalb dieser Dokumentation bedeutet das Wort "Klasse" in der Regel ein Klassenobjekt, das mit dem Schlüsselwort class erstellt wurde.

Klassendefinitionen können Variablendeklarationen, Methodendefinitionen und Unterklassendefinitionen enthalten.

Instanzvariablen [v1.1.01+]

Eine Instanzvariable ist eine Variable, von der jede Instanz der Klasse (also jedes abgeleitete Objekt der Klasse) eine eigene Kopie hat. Sie werden wie normale Zuweisungen deklariert, aber ohne dem Präfix this. (nur direkt innerhalb der Klasse):

InstanzVar := Ausdruck

Solche Deklarationen werden jedes Mal ausgewertet, wenn eine neue Instanz der Klasse mit dem new-Schlüsselwort erstellt wird. Der Methodenname __Init ist für diesen Zweck reserviert, und sollte nicht im Skript verwendet werden. Die __New()-Methode wird aufgerufen, sobald alle Deklarationen dieser Art (auch solche, die in base-Klassen definiert wurden) ausgewertet worden sind. Ausdruck kann mit this auf andere Instanzvariablen und Methoden zugreifen, aber alle anderen Variablenverweise werden als global angesehen.

Um auf eine Instanzvariable zugreifen zu können, muss immer das Zielobjekt angegeben werden; zum Beispiel this.InstanzVar.

[v1.1.08+]: Deklarationen wie x.y := z werden auch unterstützt, sofern x zuvor in dieser Klasse deklariert wurde. x := {}, x.y := 42 würde beispielsweise x deklarieren und außerdem this.x.y initialisieren.

Statische bzw. Klassenvariablen [v1.1.00.01+]

Statische bzw. Klassenvariablen gehören nur der Klasse selbst, aber sie können von abgeleiteten Objekten vererbt werden (einschließlich Unterklassen). Sie werden wie Instanzvariablen deklariert, aber unter Verwendung des static-Schlüsselworts:

static KlassenVar := Ausdruck

Statische Deklarationen werden nur einmal ausgewertet - bevor der automatische Ausführungsbereich erfolgt, und in der Reihenfolge, wie sie im Skript vorkommen. Jede Deklaration speichert einen Wert in das Klassenobjekt. Jeder Variablenverweis im Ausdruck wird als global angesehen.

Um etwas in eine Klassenvariable speichern zu können, muss immer das Klassenobjekt angegeben werden; zum Beispiel Klassenname.KlassenVar := Wert. Wenn ein Objekt x von Klassenname abgeleitet wurde und wenn x selbst nicht den Key "KlassenVar" enthält, dann kann x.KlassenVar auch verwendet werden, dynamisch den Wert von Klassenname.KlassenVar abzurufen. Allerdings würde x.KlassenVar := y den Wert in x speichern, und nicht in Klassenname.

[v1.1.08+]: Deklarationen wie static x.y := z werden auch unterstützt, sofern x zuvor in dieser Klasse deklariert wurde. static x := {}, x.y := 42 würde beispielsweise x deklarieren und außerdem Klassenname.x.y initialisieren.

Verschachtelte Klassen

Verschachtelte Klassendefinitionen ermöglichen Klassenobjekte innerhalb von anderen Klassenobjekten, anstatt eine separate globale Variable verwenden zu müssen. Im obigen Beispiel würde class VerschachtelteKlasse ein Objekt konstruieren und es in Klassenname.VerschachtelteKlasse speichern. Unterklassen können VerschachtelteKlasse erben oder es mit ihrer eigenen verschachtelten Klasse überschreiben (in diesem Fall könnte mit new this.VerschachtelteKlasse eine Instanz unabhängig einer geeigneten Klasse erstellt werden).

class VerschachtelteKlasse
{
    ...
}

Methoden

Methodendefinitionen sehen genauso aus wie Funktionsdefinitionen. Jede Methode hat einen versteckten Parameter namens this, der üblicherweise einen Verweis auf ein Objekt enthält, das von der Klasse abgeleitet wurde. Allerdings könnte er auch einen Verweis auf die Klasse selbst oder auf eine abgeleitete Klasse enthalten, je nachdem, wie die Methode aufgerufen wurde. Methoden werden per Verweis in das Klassenobjekt gespeichert.

Methode()
{
    ...
}

Innerhalb einer Methode kann das Pseudo-Schlüsselwort base verwendet werden, um auf Super-Klassen-Versionen von Methoden zugreifen zu können, oder auf Eigenschaften, die in einer abgeleiteten Klasse überschrieben werden. Zum Beispiel würde base.Methode(), wenn man es in der Klasse oben definiert, eine Version von Methode aufrufen, die über BaseKlassenname definiert wurde. Meta-Funktionen werden nicht aufgerufen; ansonsten verhält sich base.Methode() wie BaseKlassenname.Methode.Call(this). Das heißt,

base hat nur eine besondere Bedeutung, wenn ein Punkt . oder eine eckige Klammer [] danach erfolgt. Deshalb wird so etwas wie obj := base, obj.Methode() nicht funktionieren. Skripte können das spezielle Verhalten von base deaktivieren, indem sie base einen nicht-leeren Wert zuweisen; dies wird jedoch nicht empfohlen. Da die Variable base leer sein muss, könnte die Performance reduziert sein, wenn das Skript #NoEnv weglässt.

Eigenschaften [v1.1.16+]

Eigenschaftsdefinitionen erlauben das Ausführen einer Methode, wann immer das Skript einen spezifischen Key abruft oder setzt.

Eigenschaft[]
{
    get {
        return ...
    }
    set {
        return ... := Wert
    }
}

Eigenschaft ist der Name der Eigenschaft, mit dem sie aufgerufen werden kann. obj.Eigenschaft würde beispielsweise get aufrufen, und obj.Eigenschaft := Wert würde set aufrufen. Innerhalb von get oder set bezieht sich this auf das Objekt, das aufgerufen wird. Innerhalb von set enthält Wert den Wert, der zugewiesen wird.

Parameter können übergeben werden, indem man sie auf der rechten Seite des Eigenschaftsnamens in eckigen Klammern setzt, sowohl beim Definieren als auch beim Aufrufen der Eigenschaft. Abgesehen von der Verwendung eckiger Klammern werden Eigenschaftsparameter genauso wie Methodenparameter definiert - optionale, variadische und ByRef-Parameter werden unterstützt.

Der Rückgabewert von get oder set ist das Ergebnis des Unterausdrucks, der die Eigenschaft aufgerufen hat. Zum Beispiel würde Wert := obj.Eigenschaft := 42 den Rückgabewert von set in Wert speichern.

Jede Klasse kann eine oder beide Hälften einer Eigenschaft definieren. Wenn eine Klasse eine Eigenschaft überschreibt, kann sie mit base.Eigenschaft auf die Eigenschaft, die über deren base-Klasse definiert wurde, zugreifen. Wenn get oder set nicht definiert ist, kann es über eine base-Klasse behandelt werden. Wenn set nicht definiert ist und nicht über eine Meta-Funktion oder base-Klasse behandelt wird, wird es beim Zuweisen eines Wertes in das Objekt gespeichert und die Eigenschaft deaktiviert.

Intern sind get und set zwei unterschiedliche Methoden, wodurch sie sich untereinander keine Variablen teilen können (außer sie werden in this gespeichert).

Meta-Funktionen stellen eine breitere Auswahl an Möglichkeiten bereit, um auf Eigenschaften und Methoden eines Objekts zugreifen zu können, aber sie sind komplizierter und fehleranfällig.

Konstruktion und Destruktion

Immer wenn ein abgeleitetes Objekt mit dem Schlüsselwort new erstellt wird [benötigt v1.1.00+], wird die __New-Methode, definiert über das base-Objekt, aufgerufen. Diese Methode kann Parameter akzeptieren, das Objekt initialisieren und das Ergebnis des new-Operators durch Zurückgeben eines Wertes überschreiben. Zerstört man ein Objekt, wird __Delete aufgerufen. Zum Beispiel:

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

class GMem
{
    __New(aFlags, aGröße)
    {
        this.ptr := DllCall("GlobalAlloc", "uint", aFlags, "ptr", aGröße, "ptr")
        if !this.ptr
            return ""
        MsgBox % "New GMem mit einer Größe von " aGröße " Bytes auf der Adresse " this.ptr "."
        return this  ; Diese Zeile kann weggelassen werden, wenn der Operator 'new' verwendet wird.
    }

    __Delete()
    {
        MsgBox % "Delete GMem auf der Adresse " this.ptr "."
        DllCall("GlobalFree", "ptr", this.ptr)
    }
}

__Delete wird nicht bei Objekten aufgerufen, die den Key "__Class" haben. Klassenobjekte haben standardmäßig diesen Key.

Meta-Funktionen

Methodensyntax:
class Klassenname {
    __Get([Key, Key2, ...])
    __Set([Key, Key2, ...], Value)
    __Call(Name [, Parameter...])
}

Funktionssyntax:
MeinGet(this [, Key, Key2, ...])
MeinSet(this [, Key, Key2, ...], Value)
MeinCall(this, Name [, Parameter...])

Klassenname := { __Get: Func("MeinGet"), __Set: Func("MeinSet"), __Call: Func("MeinCall") }

Meta-Funktionen definieren, was passieren soll, wenn ein Key angefordert, aber nicht im Zielobjekt gefunden wird. Wenn obj.key beispielsweise leer wäre, würde es die __Get-Meta-Funktion aufrufen. Außerdem würde obj.key := value die Meta-Funktion __Set aufrufen und obj.key() die Meta-Funktion __Call. Diese Meta-Funktionen (oder Methoden) müssten in obj.base, obj.base.base oder ähnliches definiert werden.

Wenn das Skript einen nicht vorhandenen Key im Zielobjekt abruft (get), setzt (set) oder aufruft (call), wird das base-Objekt wie folgt aufgerufen:

Wenn eine Meta-Funktion einen passenden Key im Objekt beinhaltet, aber keinen return verwendet, ist das Verhalten das gleiche wie, als wäre der Key bereits zu Beginn im Objekt da gewesen. Um zu erfahren, wie __Set funktioniert, siehe Mehrdimensionale Arrays bei Unterklassen.

Wenn die Operation immer noch nicht behandelt wurde, überprüfe, ob es eine Built-in-Methode oder -Eigenschaft ist:

Wenn die Operation immer noch nicht behandelt wurde,

Bekannte Einschränkung:

Dynamische Eigenschaften

Mit der Eigenschaftssyntax können Eigenschaften definiert werden, die jedes Mal, wenn sie ausgewertet werden, einen Wert berechnen, aber jede Eigenschaft muss im Voraus bekannt und einzeln im Skript definiert sein. Mit __Get und __Set können hingegen Eigenschaften implementiert werden, die das Skript nicht vorher kennen muss.

Zum Beispiel könnte man ein "Proxy"-Objekt erstellen, das Eigenschaften über das Netzwerk (oder über einen beliebig anderen Kanal) anfordern könnte. Ein Remote-Server würde dann mit dem Wert von der Eigenschaft antworten, den das Proxy-Objekt dann an seinen Aufrufer weiterleiten könnte. Auch wenn der Name jeder Eigenschaft im Voraus bekannt war, wäre es unlogisch, jede Eigenschaft einzeln in der Proxy-Klasse zu definieren, da jede Eigenschaft dasselbe tun würde (einen Netzwerk-Request senden). Meta-Funktionen erhalten die Eigenschaftsnamen als Parameter und wären daher eine gute Lösung für dieses Problem.

Eine weitere Verwendungsmöglichkeit von __Get und __Set wäre es, eine Reihe von verwandten Eigenschaften zu implementieren, die Code unter sich aufteilen. In dem Beispiel unten werden sie verwendet, um ein "Farbe"-Objekt mit R, G, B und RGB-Eigenschaften zu implementieren, wo tatsächlich nur der RGB-Wert gespeichert wird:

rot := new Farbe(0xff0000), rot.R -= 5
cyan := new Farbe(0), cyan.G := 255, cyan.B := 255

MsgBox % "rot: " rot.R "," rot.G "," rot.B " = " rot.RGB
MsgBox % "cyan: " cyan.R "," cyan.G "," cyan.B " = " cyan.RGB

class Farbe
{
    __New(aRGB)
    {
        this.RGB := aRGB
    }

    static Verschiebung := {R:16, G:8, B:0}

    __Get(aName)
    {
        ; HINWEIS: this.Verschiebung würde hier eine Endlosschleife erzeugen!
        Verschiebung := Farbe.Verschiebung[aName]  ; Ermittelt die Anzahl der zu verschiebenden Bits.
        if (Verschiebung != "")  ; Ist sie eine bekannte Eigenschaft?
            return (this.RGB >> Verschiebung) & 0xff
        ; HINWEIS: Ein 'return' hier würde this.RGB kaputtmachen.
    }

    __Set(aName, aWert)
    {
        if ((Verschiebung := Farbe.Verschiebung[aName]) != "")
        {
            aWert &= 255  ; Kürzt es auf eine geeignete Länge.

            ; Errechnet und speichert den neuen RGB-Wert.
            this.RGB := (aWert << Verschiebung) | (this.RGB & ~(0xff << Verschiebung))

            ; 'Return' ist notwendig, um die Erstellung eines neuen Key-Value-Paares zu verhindern.
            ; Dies bestimmt auch, was in dem 'x' in 'x := clr[name] := val' gespeichert wird:
            return aWert
        }
        ; HINWEIS: Ein 'return' hier würde this._RGB und this.RGB kaputtmachen.
    }

    ; Meta-Funktionen können mit Eigenschaften vermischt werden:
    RGB {
        get {
            ; Als Hex-Wert zurückgeben:
            return format("0x{:06x}", this._RGB)
        }
        set {
            return this._RGB := value
        }
    }
}

In diesem Fall hätte man stattdessen die Eigenschaftssyntax verwenden können, wo Code untereinander aufgeteilt werden kann, indem man jede Eigenschaft eine zentrale Methode aufrufen lässt. Meta-Funktionen sollte man wenn möglich vermeiden, da ein hohes Risiko besteht, dass sie falsch verwendet werden (siehe die roten Hinweise oben).

Objekte als Funktionen

Siehe Funktionsobjekte, wie Objekte erstellt werden können, die sich wie Funktionen verhalten.

Ein Funktionsobjekt kann sich auch wie eine Meta-Funktion verhalten, um z. B. dynamische Eigenschaften zu definieren, die denen im vorherigen Abschnitt ähneln. Obwohl man die Eigenschaftssyntax immer wenn möglich verwenden sollte, zeigt das Beispiel unten das Potenzial von Meta-Funktionen, um neue Konzepte oder Verhaltensmuster zu implementieren, oder um die Struktur des Skripts zu ändern.

blau := new Farbe(0x0000ff)
MsgBox % blau.R "," blau.G "," blau.B

class Eigenschaften extends Funktionsobjekt
{
    __Call(aTarget, aName, aParams*)
    {
        ; Falls dieses Eigenschaftsobjekt eine Definition für diese Halb-Eigenschaft enthält, wird es aufgerufen.
        if ObjHasKey(this, aName)
            return this[aName].Call(aZiel, aParams*)
    }
}

class Farbe
{
    __New(aRGB)
    {
        this.RGB := aRGB
    }

    class __Get extends Eigenschaften
    {
        R() {
            return (this.RGB >> 16) & 255
        }
        G() {
            return (this.RGB >> 8) & 255
        }
        B() {
            return this.RGB & 255
        }
    }

    ;...
}

Mehrdimensionale Arrays bei Unterklassen

Wenn eine Multiparameter-Zuweisung wie Tabelle[x, y] := Inhalt indirekt bewirken sollte, dass ein neues Objekt erstellt wird, hat das neue Objekt kein base und folglich keine benutzerdefinierten Methoden oder Sonderverhalten. Mit __Set könnten diese Objekte wie folgt initialisiert werden.

x := {base: {addr: Func("x_Addr"), __Set: Func("x_Setter")}}

; Weist einen Wert zu, und ruft indirekt x_Setter auf, um Unterobjekte zu erstellen.
x[1,2,3] := "..."

; Ermittelt den Wert und ruft die Beispielmethode auf.
MsgBox % x[1,2,3] "`n" x.addr() "`n" x[1].addr() "`n" x[1,2].addr()

x_Setter(x, p1, p2, p3) {
    x[p1] := new x.base
}

x_Addr(x) {
    return &x
}

Da x_Setter vier Pflichtparameter hat, wird es nur aufgerufen, wenn zwei oder mehr Key-Parameter vorhanden sind. Erfolgt die oben genannte Zuweisung, findet folgendes statt:

Standard-base-Objekt

Verwendet man ein Nicht-Objekt-Wert in Verbindung mit der Objektsyntax, wird das Standard-base-Objekt aufgerufen. Dies könnte zum einen fürs Debuggen nützlich sein, oder, um ein objektähnliches Verhalten für Zeichenketten, Zahlen und/oder Variablen global definieren zu können. Möchte man auf das Standard-base zugreifen, könnte man .base in Verbindung mit einem beliebigen Nicht-Objekt-Wert verwenden; zum Beispiel "".base. Das Standard-base kann nicht wie in "".base := Object() gesetzt werden. Allerdings kann das Standard-base selbst auch ein base haben, wie in "".base.base := Object().

Automatische Variableninitialisierung

Dient eine leere Variable als Ziel einer Set-Operation, wird sie direkt an die __Set-Meta-Funktion übergeben, so dass sie ein neues Objekt in die Variable einfügen kann. Aus Platzgründen unterstützt dieses Beispiel nicht mehr als ein Parameter; mit einer variadischen Funktion wäre das möglich.

"".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)
}

Pseudo-Eigenschaften

Diese Art von Syntax kann auch auf Zeichenketten und Zahlen angewendet werden.

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

MsgBox % A_AhkPath.length " == " StrLen(A_AhkPath)
MsgBox % A_AhkPath.length.is("integer")

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

Default_is(nonobj, type)
{
    if nonobj is %type%
        return true
    return false
}

Beachte, dass Built-in-Funktionen auch möglich sind, aber in diesem Fall können die Klammern nicht weggelassen werden:

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

Debuggen

Falls ein Wert nicht als Objekt behandelt werden soll, kann eine Warnmeldung angezeigt werden, wann immer ein Nicht-Objekt-Wert aufgerufen wird:

"".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 Ein Nicht-Objekt-Wert wurde unsachgemäß aufgerufen.`n`nSpeziell: %nonobj%
}

Implementierung

Referenzzählung

AutoHotkey verwendet einen einfachen Referenzzählungsmechanismus, um Ressourcen eines Objekts freizugeben, das nicht länger im Skript gebraucht wird. Skript-Autoren sollten diesen Mechanismus nur aufrufen, wenn man direkt mit unverwalteten Pointer, die auf Objekte verweisen, arbeiten muss.

In AutoHotkey v1.1 werden temporäre Verweise, die innerhalb eines Ausdrucks erstellt (aber nirgendwo gespeichert) werden, direkt nach ihrer Verwendung freigegeben. Zum Beispiel würde Fn(&{}) eine ungültige Adresse an die Funktion übergeben, weil der temporäre Verweis, zurückgegeben von {}, direkt nach dem Auswerten des Adresse-von-Operators freigegeben wird.

Wenn Code beim Freigeben des letzten Verweises auf ein Objekt ausgeführt werden soll, muss man die __Delete-Meta-Funktion implementieren.

Bekannte Einschränkungen:

Das Betriebssystem kann den benutzten Speicher vom Objekt wieder verwenden, sobald das Programm beendet wird. Allerdings wird __Delete erst aufgerufen, wenn alle Verweise auf das Objekt freigegeben worden sind. Das kann wichtig sein, wenn andere Ressourcen freigegeben werden, die nicht automatisch vom Betriebssystem wieder verwendet werden, wie zum Beispiel temporäre Dateien.

Pointer auf Objekte

In einigen seltenen Fällen kann es erforderlich sein, ein Objekt per DllCall an einen externen Code zu übergeben oder ein Objekt in eine binäre Datenstruktur zu speichern, um es später abrufen zu können. Die Adresse eines Objekts kann via Adresse := &Objekt abgerufen werden; dies würde allerdings effektiv zwei Verweise auf das Objekt erzeugen, aber das Programm kennt nur den einen in Objekt. Wenn der letzte bekannte Verweis auf das Objekt freigegeben wurde, wird das Objekt gelöscht. Demzufolge muss das Skript das Objekt darüber informieren, dass es einen Verweis erhalten hat. Es gibt zwei Wege, um das zu erreichen:

; Methode #1: Referenzzählung explizit erhöhen.
Adresse := &Objekt
ObjAddRef(Adresse)

; Methode #2: Object() benutzen, das die Referenzzählung erhöht und eine Adresse zurückgibt.
Adresse := Object(Objekt)

Mit dieser Funktion kann man auch eine Adresse wieder in ein Verweis umwandeln:

Objekt := Object(Adresse)

In jedem Fall muss das Skript das Objekt außerdem darüber informieren, dass es mit diesem Verweis fertig ist:

; Verringert die Referenzzählung des Objekts, damit es freigegeben werden kann:
ObjRelease(Adresse)

Generell sollte jede neue Kopie einer Objektadresse als seperater Objektverweis behandelt werden, demzufolge sollte das Skript ObjAddRef aufrufen, wenn es eine Kopie erhält, und sofort ObjRelease aufrufen, bevor es eine verliert. Würde man beispielsweise eine Adresse mit so etwas wie x := Adresse kopieren, sollte ObjAddRef aufgerufen werden. Ebenso sollte das Skript ObjRelease aufrufen, wenn es mit x fertig ist (oder dabei ist den Wert von x zu überschreiben).

Beachte, dass die Object()-Funktion sogar auf Objekte angewendet werden kann, die sie selbst nicht erstellt hat, wie z. B. COM-Objekt-Wrapper oder File-Objekte.