Objekte

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

Verwandte Themen:

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

Ergebnis := IsObject(Ausdruck)

Eine Liste der standardmäßigen Objekttypen finden Sie unter Objekttypen in der Seitenleiste der Dokumentation. Es gibt drei grundlegende Typen:

Objekte werden ab [AHK_L 31] unterstützt, allerdings erfordern einige Features eine neuere Version.

Inhaltsverzeichnis

Grundlegende Verwendung

Einfache Arrays [v1.1.21+]

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 eines oder mehrerer Elemente ab einem bestimmten Index mittels InsertAt-Methode:

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

Anfügen eines oder mehrerer Elemente mittels Push-Methode:

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

Entfernen eines Elements mittels RemoveAt-Methode:

EntfernterWert := Array.RemoveAt(Index)

Entfernen eines Elements mittels Pop-Methode:

EntfernterWert := Array.Pop()

MinIndex und MaxIndex/Length geben, sofern das Array nicht leer ist, den niedrigsten und höchsten Index des Arrays zurück. Während der niedrigste Index fast immer 1 ist, wird MaxIndex üblicherweise die Anzahl der Elemente zurückgeben. Wenn keine Integer-Schlüssel vorhanden sind, wird MaxIndex eine leere Zeichenkette und Length eine 0 zurückgeben. Um die Inhalte des Arrays zu durchlaufen, erfolgt dies entweder via Index oder mit einer 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 [v1.1.21+]

Ein assoziatives Array ist ein Objekt, das eine Sammlung von eindeutigen Schlüsseln und eine Sammlung von Werten enthält, die jeweils miteinander verbunden sind. Schlüssel können Zeichenketten, Integer oder Objekte sein, während Werte von beliebigem Typ sein können. Ein assoziatives Array kann wie folgt erstellt werden:

Array := {SchlüsselA: WertA, SchlüsselB: WertB, ..., SchlüsselZ: WertZ}
Array := Object("SchlüsselA", WertA, "SchlüsselB", WertB, ..., "SchlüsselZ", WertZ)

Bei der {Schlüssel:Wert}-Schreibweise muss der Schlüssel, sofern dieser nur aus Wortzeichen besteht, nicht in Anführungszeichen gesetzt werden. Der Schlüssel kann ein beliebiger Ausdruck sein, aber um eine Variable als Schlüssel zu benutzen, muss sie von runden Klammern umschlossen sein. Zum Beispiel wäre sowohl {(SchlüsselVar): Wert} als auch {GetKey(): Wert} eine gültige Angabe.

Abrufen eines Elements:

Wert := Array[Schlüssel]

Zuweisen eines Elements:

Array[Schlüssel] := Wert

Entfernen eines Elements mittels Delete-Methode:

EntfernterWert := Array.Delete(Schlüssel)

Enumerieren von Elementen:

array := {zehn: 10, zwanzig: 20, dreißig: 30}
For Schlüssel, Wert in Array
    MsgBox %Schlüssel% = %Wert%

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

In AutoHotkey v1.x sind einfache Arrays und assoziative Arrays funktionsgemäß identisch. Solange man aber [] als einfaches lineares Array behandelt, wird sichergestellt, dass dessen Rolle eindeutig bleibt, und die Chance erhöht, dass Ihr Skript auch mit einer zukünftigen Version von AutoHotkey noch funktionieren wird, wo der Unterschied zwischen einfachem und assoziativem Array eventuell erkennbar sein muss.

Objekte [AHK_L 31+]

Bei allen Objekttypen kann die Schreibweise Objekt.DirektGeschriebenerSchlüssel benutzt werden, um auf eine Eigenschaft, ein Array oder eine Methode zuzugreifen. Hierbei ist DirektGeschriebenerSchlüssel ein Identifikator oder Integer und Objekt ein beliebiger Ausdruck. Identifikatoren sind anführungszeichenlose Zeichenketten, die aus alphanumerischen Zeichen, Unterstrich und seit [v1.1.09] aus ASCII-fremden Zeichen bestehen können. Zum Beispiel wäre match.Pos das gleiche wie match["Pos"], oder arr.1 das gleiche wie arr[1]. Es darf kein Leerzeichen nach dem Punkt erfolgen.

Beispiele:

Abrufen einer Eigenschaft:

Wert := Objekt.Eigenschaft

Setzen einer Eigenschaft:

Objekt.Eigenschaft := Wert

Aufrufen einer Methode:

RückgabeWert := Objekt.Methode(Params)

Aufrufen einer Methode mit einem errechneten Methodennamen:

RückgabeWert := Objekt[MethodeName](Params)

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

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

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

Bekannte Einschränkung:

Freigeben von Objekten

Skripte geben Objekte nicht explizit frei. Sobald die letzte Referenz zu einem Objekt freigegeben ist, wird das Objekt automatisch freigegeben. Eine Referenz, die in einer Variable gespeichert ist, wird automatisch freigegeben, wenn diese Variable irgendeinen anderen Wert zugewiesen bekommt. Zum Beispiel:

obj := {}  ; Erstellt ein Objekt.
obj := ""  ; Gibt die letzte Referenz frei, wodurch das Objekt freigegeben wird.

Außerdem wird eine Referenz, die in einem Feld eines anderen Objekts gespeichert ist, freigegeben, wenn dieses Feld irgendeinen anderen Wert zugewiesen bekommt oder aus dem Objekt entfernt wird. Dies gilt auch für Arrays, weil sie quasi das gleiche wie 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 das zweite Objekt und gibt es frei.

Da alle Referenzen zu einem 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 eine Referenz zum Child-Objekt enthält, und umgekehrt. Um diese Situation in den Griff zu bekommen, entfernen Sie den Zirkelbezug.

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 := ""             ; Wenn die Zeile darüber fehlen würde, könnten die Objekte auf diese Weise nicht freigegeben werden.

Für mehr Details, siehe Referenzzählung.

Bemerkungen

Syntax

Alle Objekttypen unterstützen sowohl die Arraysyntax (eckige Klammern) als auch die Objektsyntax (Punkte).

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

Ein Objekt wird als leere Zeichenkette behandelt, wenn es in einem Kontext verwendet wird, wo ein Objekt nicht erwartet wird. Zum Beispiel würde MsgBox %objekt% eine leere MsgBox anzeigen und objekt + 1 eine leere Zeichenkette zurückgeben. Verlassen Sie sich nicht auf dieses Verhalten, weil es noch geändert werden könnte.

Wenn unmittelbar nach einem Methodenaufruf ein Zuweisungsoperator erfolgt, werden die runden Klammern als eckige Klammern behandelt, demzufolge wäre dies das gleiche wie, als würde man Parameter für eine Eigenschaft setzen. Zum Beispiel sind die folgenden Zeilen funktionsgemäß identisch:

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 damit Standardwerte für Parameter in Eigenschaften und Metafunktionen definieren. Der Methodenname kann zudem komplett weggelassen werden, wie z. B. in x[](a). Skripte können damit einen Standardwert für den ersten Parameter der __Call-Metafunktion definieren, um zu verhindern, dass er ohne Wert weitergereicht wird. Beachten Sie, dass diese Schreibweise einen anderen Effekt hat als x.(a), das x[""](a) entspricht. Wenn man den Eigenschafts- oder Methodennamen beim Aufrufen eines COM-Objekts weglässt, wird sein "Standardelement" aufgerufen.

Schlüssel (Keys)

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

Erweiterte Verwendung

Funktionsreferenzen [v1.1.00+]

Wenn die folgende Variable funk den Namen einer Funktion enthält, kann die Funktion wie folgt aufgerufen werden: %funk%() oder funk.(). Allerdings ist dies ineffizient, weil jedes Mal, wenn die Funktion aufgerufen wird, der Funktionsname aufgelöst werden muss. Um die Leistung zu verbessern, kann eine Referenz zu der Funktion in eine Variable gespeichert und später wiederverwendet werden:

Funk := Func("MeineFunk")

Mit der folgenden Syntax kann eine Funktion via Referenz aufgerufen werden:

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

Weitere Eigenschaften von Funktionsreferenzen finden Sie unter Func-Objekt.

Mehrdimensionale Arrays

AutoHotkey unterstützt "mehrdimensionale" Arrays, indem Arrays in andere Arrays gespeichert werden. Zum Beispiel repräsentieren die Zeilen einer Tabelle die Felder eines Arrays, während die Felder selbst die Spalten der jeweiligen Zeile in Form eines Arrays enthalten. In diesem Fall kann der Inhalt der Spalte y von der Zeile 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 via 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 -referenzen enthält. Zum Beispiel:

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

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

; Rufe jede Funktion auf und übergebe das Array selbst als indirekten Parameter:
Loop 2
    array[A_Index]()

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

Aus Gründen der Abwärtskompatibilität wird die zweite Form das array nicht als Parameter übergeben, wenn array[A_Index] einen Funktionsnamen anstelle einer Funktionsreferenz enthält. Wenn aber array[A_Index] von array.base[A_Index] geerbt wurde, wird das array als Parameter übergeben.

Benutzerdefinierte Objekte

Vom Skript erstellte Objekte müssen nicht zwingend eine vordefinierte Struktur haben. Stattdessen kann jedes Objekt Eigenschaften und Methoden von seinem Basisobjekt, auch bekannt als "Prototyp" oder "Klasse", erben. Einem Objekt können jederzeit Eigenschaften und Methoden hinzugefügt (oder entfernt) werden; solche Änderungen haben auf alle abgeleiteten Objekte eine Auswirkung. Für komplexere oder speziellere Situationen kann ein Basisobjekt mithilfe von Metafunktionen das Standardverhalten von jedem abgeleiteten Objekt überschreiben.

Basisobjekte sind ganz normale Objekte, die wie folgt erstellt werden können:

class Basisobjekt {
    static foo := "bar"
}
; ODER
Basisobjekt := {foo: "bar"}

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

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

Es ist jederzeit möglich, einem Objekt eine neue Basis zuzuweisen, um alle Eigenschaften und Methoden, die das Objekt geerbt hat, nachhaltig zu ersetzen.

Prototypen

Prototypen oder Basisobjekte können wie jedes andere Objekt konstruiert und manipuliert werden. Das folgende Beispiel zeigt, wie ein gewöhnliches Objekt mit einer Eigenschaft und einer Methode konstruiert werden kann:

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

obj_test(this) {
    MsgBox % this.foo
}

Sobald obj.test() aufgerufen wird, 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 Referenz) in das Objekt gespeichert wird (statt von einem Basisobjekt geerbt zu werden). Standardmäßig wird der Name der Funktion aus folgenden Komponenten gebildet: der "Typ" des Objekts und der Name der Methode.

Ein Objekt zählt als Prototyp oder Basis, wenn es von einem anderen Objekt abgeleitet wird:

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

In diesem Fall erbt anderesObj das foo und test von obj. Diese Vererbung ist dynamisch - das heißt, wenn obj.foo modifiziert wird, wird anderesObj.foo diese Änderung widerspiegeln. Wenn das Skript anderesObj.foo einen Wert zuweist, wird dieser Wert in anderesObj gespeichert; weitere Änderungen an obj.foo hätten keinen Einfluss auf anderesObj.foo. Sobald anderesObj.test() aufgerufen wird, enthält ihr this-Parameter eine Referenz zu anderesObj statt zu obj.

Klassen [v1.1.00+]

Eine "Klasse" ist im Grunde eine Gruppe oder Kategorie von Dingen, die Eigenschaften oder Attribute gemeinsam nutzen. Da ein Basisobjekt oder Prototypobjekt Eigenschaften und Verhaltensweisen für eine Gruppe von Objekten definiert, kann es auch als Klassenobjekt bezeichnet werden. Das folgende Beispiel zeigt, wie auf einfache Weise Basisobjekte mithilfe des Schlüsselworts "class" definiert werden können:

class KlasseName extends BasisklasseName
{
    InstanzVar := Ausdruck
    static KlasseVar := Ausdruck

    class VerschachtelteKlasse
    {
        ...
    }

    Methode()
    {
        ...
    }

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

Dieses Beispiel konstruiert, nachdem das Skript geladen wurde, ein Objekt und speichert es in die globale (oder [in v1.1.05+] in die superglobale) Variable KlasseName. Um diese Klasse innerhalb einer Force-Local-Funktion (oder vor [v1.1.05] innerhalb einer Assume-Local- oder Assume-Static-Funktion) zu verweisen, ist eine Deklaration wie global KlasseName notwendig. Wenn extends BasisklasseName vorhanden ist, muss der BasisklasseName der vollständige Name einer anderen Klasse sein (in [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.

Da die Klasse via Variable verwiesen wird, kann der Klassenname nicht genutzt werden, um im selben Kontext sowohl die Klasse zu verweisen als auch eine separate Variable zu erstellen (um z. B. eine Instanz der Klasse zu enthalten). Zum Beispiel würde box := new Box das Klassenobjekt in Box mit einer Instanz von sich selbst ersetzen. [v1.1.27+]: #Warn ClassOverwrite aktiviert eine Warnung, die angezeigt wird, wenn Sie versuchen, eine Klasse zu überschreiben.

In dieser Dokumentation bezieht sich das Wort "Klasse" in der Regel auf 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 Objekt, das von der Klasse abgeleitet wurde) eine eigene Kopie hat. Sie werden wie normale Zuweisungen deklariert, ohne dass das Präfix this. angegeben werden muss (dies gilt nur in der Klassendefinition):

InstanzVar := Ausdruck

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

Um Zugriff auf eine Instanzvariable zu erhalten (sogar innerhalb einer Methode), müssen Sie immer das Zielobjekt angeben; 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, die aber von abgeleiteten Objekten (einschließlich Unterklassen) geerbt werden können. Solche Variablen müssen mit dem Schlüsselwort "static" deklariert werden:

static KlasseVar := 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. Alle Variablenreferenzen in Ausdruck werden als global angesehen.

Um einer Klassenvariable etwas zuzuweisen, müssen Sie immer das Klassenobjekt angeben; zum Beispiel KlasseName.KlasseVar := Wert. Wenn ein Objekt x von KlasseName abgeleitet wurde und wenn x selbst nicht den Schlüssel "KlasseVar" enthält, kann mit x.KlasseVar zudem der Wert von KlasseName.KlasseVar dynamisch abgerufen werden. Allerdings würde x.KlasseVar := y den Wert nicht in KlasseName, sondern in x speichern.

[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 KlasseName.x.y initialisieren.

Verschachtelte Klassen

Mit verschachtelten Klassendefinitionen können Klassenobjekte innerhalb von anderen Klassenobjekten gespeichert werden, ohne dass separat eine globale Variable verwendet werden muss. Im obigen Beispiel konstruiert class VerschachtelteKlasse ein Objekt und speichert es in KlasseName.VerschachtelteKlasse. Unterklassen können VerschachtelteKlasse erben oder es mit ihrer eigenen verschachtelten Klasse überschreiben (in diesem Fall kann mit new this.VerschachtelteKlasse eine geeignete Klasse instanziiert werden).

class VerschachtelteKlasse
{
    ...
}

Methoden

Methodendefinitionen sehen genauso aus wie Funktionsdefinitionen. Jede Methode hat einen versteckten Parameter namens this, der üblicherweise eine Referenz zu einem Objekt enthält, das von der Klasse abgeleitet wurde. Allerdings kann dieser Parameter auch eine Referenz zu der Klasse selbst oder zu einer abgeleiteten Klasse enthalten, je nachdem, wie die Methode aufgerufen wurde. Methoden werden per Referenz in das Klassenobjekt gespeichert.

Methode()
{
    ...
}

Innerhalb einer Methode kann das Pseudo-Schlüsselwort base verwendet werden, um auf Super-Klassen-Versionen von Methoden oder auf Eigenschaften, die in einer abgeleiteten Klasse überschrieben werden, zugreifen zu können. base.Methode() in der Klasse oben würde beispielsweise bewirken, dass eine Version von Methode aufgerufen wird, die über BasisklasseName definiert wurde. Metafunktionen werden nicht aufgerufen; ansonsten verhält sich base.Methode() wie BaseKlasseName.Methode.Call(this). Das heißt,

base hat nur eine besondere Bedeutung, wenn danach ein Punkt . oder eine eckige Klammer [] erfolgt, demzufolge wird so etwas wie obj := base, obj.Methode() nicht funktionieren. Skripte können das spezielle Verhalten von base deaktivieren, indem sie dieser Sondervariable einen nicht-leeren Wert zuweisen; dies wird jedoch nicht empfohlen. Da die Variable base leer sein muss, kann die Leistung 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 Schlüssel abruft oder setzt.

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

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 value den Wert, der zugewiesen wird.

Beim Definieren oder Aufrufen einer Eigenschaft können Parameter übergeben werden, indem man sie auf der rechten Seite des Eigenschaftsnamens in eckigen Klammern setzt. Abgesehen von den eckigen 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 eines Teilausdrucks, 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 base.Eigenschaft benutzen, um auf die Eigenschaft zuzugreifen, die von ihrer Basisklasse definiert wurde. Wenn get oder set nicht definiert ist, kann es von einer Basisklasse behandelt werden. Wenn set nicht definiert ist und nicht von einer Metafunktion oder Basisklasse behandelt wird, führt das Zuweisen eines Wertes dazu, dass dieser Wert in das Objekt gespeichert wird, wodurch die Eigenschaft deaktiviert wird.

Intern sind get und set zwei unterschiedliche Methoden, die sich untereinander keine Variablen teilen können (es sei denn, man speichert sie in this).

Metafunktionen bieten eine breitere Auswahl an Möglichkeiten für den kontrollierten Zugriff auf Eigenschaften und Methoden eines Objekts, allerdings sind sie komplizierter und fehleranfälliger.

Konstruktion und Destruktion

Wenn ein abgeleitetes Objekt mit dem Schlüsselwort new erstellt wird [benötigt v1.1.00+], wird die __New-Methode aufgerufen, die von seinem Basisobjekt definiert wurde. 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 Schlüssel "__Class" haben. Klassenobjekte haben standardmäßig diesen Schlüssel.

Wenn die Klasse eine Superklasse hat, die diese Methoden definiert, sollte base.__New() (mit entsprechenden Parametern) und base.__Delete() üblicherweise aufgerufen werden. Ansonsten wird nur die am weitesten abgeleitete Definition der Methode aufgerufen, außer Definitionen innerhalb des Zielobjekts selbst.

[v1.1.28+]: Wenn eine Ausnahme oder ein Laufzeitfehler ausgelöst wird, während __Delete ausgeführt wird, und nicht innerhalb von __Delete behandelt wird, verhält es sich so, als wäre __Delete von einem neuen Thread aufgerufen wurden. Das heißt, dass ein Fehlerdialogfenster angezeigt und __Delete durchgeführt wird, aber der Thread nicht beendet wird (es sei denn, er wurde bereits beendet). Vor v1.1.28 war es so, dass unbehandelte Ausnahmen ein inkonsistentes Verhalten verursachten.

Wenn das Skript auf irgendeine Weise direkt terminiert wird, z. B. über das Tray-Menü, ExitApp oder Exit (wenn das Skript nicht persistent ist), erhalten alle Funktionen, die noch nicht zu ihrem Aufrufer zurückgekehrt sind, keine Chance, dies jemals zu tun. Daher werden alle Objekte, die von lokalen Variablen dieser Funktionen referenziert werden, nicht freigegeben, was zur Folge hat, dass __Delete nicht aufgerufen wird.

Wenn das Skript beendet wird, werden Objekte, die in globalen und statischen Variablen enthalten sind, automatisch in einer willkürlichen, von der Implementierung festgelegten Reihenfolge freigegeben. Wenn __Delete während dieses Vorgangs aufgerufen wird, können einige globale oder statische Variablen bereits freigegeben worden sein, aber alle Referenzen, die das Objekt selbst enthält, sind nach wie vor gültig. Daher ist es am besten, wenn __Delete völlig eigenständig ist und nicht auf globale oder statische Variablen angewiesen ist.

Metafunktionen

Methodensyntax:
class KlasseName {
    __Get([Schlüssel, Schlüssel2, ...])
    __Set([Schlüssel, Schlüssel2, ...], Wert)
    __Call(Name [, Parameter...])
}

Funktionssyntax:
MeinGet(this [, Schlüssel, Schlüssel2, ...])
MeinSet(this [, Schlüssel, Schlüssel2, ...], Wert)
MeinCall(this, Name [, Parameter...])

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

Metafunktionen definieren, was passieren soll, wenn ein Schlüssel angefordert, aber nicht im Zielobjekt gefunden wird. Wenn einem obj.Schlüssel beispielsweise keinen Wert zugewiesen wurde, wird die __Get-Metafunktion aufgerufen. obj.Schlüssel := Wert veranlasst hingegen den Aufruf von __Set und obj.Schlüssel() den Aufruf von __Call. Diese Metafunktionen (oder Methoden) sollten in obj.base, obj.base.base oder so ähnlich definiert sein.

Metafunktionen werden in der Regel wie Methoden definiert, folgen aber nicht den gleichen Regeln (außer wenn sie explizit vom Skript aufgerufen werden). Sie müssen in einem Basisobjekt definiert sein; jede Definition im Zielobjekt selbst wird ignoriert. Jede auf das Zielobjekt anwendbare Definition von __Get, __Set und __Call wird automatisch gemäß den folgenden Regeln aufgerufen und sollte nicht base.__Get(Schlüssel) oder ähnlich aufrufen. __New und __Delete müssen in einem Basisobjekt definiert sein, verhalten sich aber ansonsten wie Methoden.

Hinweis: AutoHotkey v2 ersetzt Metafunktionen mit Methoden, die konventioneller sind.

Wenn das Skript einen nicht vorhandenen Schlüssel im Zielobjekt abruft (get), setzt (set) oder aufruft (call), wird das Basisobjekt wie folgt aufgerufen:

Wenn eine Metafunktion einen passenden Schlüssel im Objekt beinhaltet, aber keinen return verwendet, verhält sie sich so, als wäre der Schlüssel 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 interne 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, allerdings muss jede Eigenschaft im Voraus bekannt sein und einmalig im Skript vorkommen. Mit __Get und __Set können hingegen Eigenschaften implementiert werden, die nicht im Voraus bekannt sein müssen.

Zum Beispiel kann ein "Proxy-Objekt" mit Eigenschaften erstellt werden, die den aktuellen Wert über das Netzwerk (oder über einen anderen Kanal) abfragen. Ein Remote-Server sendet eine Antwort zurück, die den Wert der Eigenschaft enthält, und das Proxy-Objekt übergibt den Wert an seinen Aufrufer. 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 (eine Netzwerkanfrage senden). Metafunktionen bekommen den Namen der Eigenschaft als Parameter, demzufolge sind sie eine gute Lösung für dieses Problem.

__Get und __Set sind auch nützlich, um 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 Shift := {R:16, G:8, B:0}

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

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

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

            ; 'Return' ist notwendig, um die Erstellung eines neuen Schlüssel-Wert-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.gespeichertes_RGB und this.RGB unbrauchbar machen.
    }

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

In diesem Fall hätte man stattdessen die Eigenschaftssyntax verwenden können, um den Code unter den Eigenschaften aufteilen zu lassen, indem sie alle eine zentrale Methode aufrufen. Metafunktionen 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 geändert werden können, um sie wie Funktionen verhalten zu lassen.

Ein Funktionsobjekt kann sich auch wie eine Metafunktion verhalten, um z. B. dynamische Eigenschaften wie im vorherigen Abschnitt zu definieren. Obwohl es ratsam ist, immer die Eigenschaftssyntax zu benutzen, zeigt das folgende Beispiel, wie nützlich Metafunktionen sein können, um neue Konzepte oder Verhaltensmuster zu implementieren, oder um die Struktur des Skripts zu ändern.

; Dieses Beispiel benötigt die FunktionObjekt-Klasse, um zu funktionieren.
blau := new Farbe(0x0000ff)
MsgBox % blau.R "," blau.G "," blau.B

class Eigenschaften extends FunktionObjekt
{
    Call(aZiel, aName, aParams*)
    {
        ; Falls dieses Eigenschaften-Objekt eine Definition für diese Halb-Eigenschaft enthält, rufe es auf.
        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

Ein Objekt, das indirekt via Multiparameter-Zuweisung wie Tabelle[x, y] := Inhalt erstellt wird, hat in der Regel keine Basis und folglich keine benutzerdefinierten Methoden oder Sonderverhalten. Mit __Set können solche 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
}

x_Setter hat vier Pflichtparameter und kann daher nur aufgerufen werden, wenn zwei oder mehr Schlüsselparameter vorhanden sind. Erfolgt die oben genannte Zuweisung, findet folgendes statt:

Standardbasisobjekt

Wenn ein objektloser Wert mit der Objektsyntax benutzt wird, wird das Standardbasisobjekt aufgerufen. Dieses Objekt ist nützlich, um das Skript zu debuggen oder um Zeichenketten, Zahlen und/oder Variablen in globaler Hinsicht ein objektähnliches Verhalten zu geben. Der Zugriff auf die Standardbasis kann nur erfolgen, wenn .base mit irgendeinem objektlosen Wert verwendet wird; zum Beispiel "".base. Obwohl die Standardbasis nicht wie in "".base := Object() gesetzt werden kann, kann es selbst eine Basis wie in "".base.base := Object() haben.

Automatische Variableninitialisierung

Wenn eine leere Variable als Ziel einer Set-Operation bestimmt wurde, wird sie direkt an die __Set-Metafunktion übergeben, damit 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
}

Beachten Sie, dass interne Funktionen auch genutzt werden können, aber in diesem Fall dürfen die runden Klammern nicht weggelassen werden:

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

Debuggen

Das folgende Beispiel zeigt, wie eine Warnmeldung angezeigt werden kann, wann immer ein objektloser Wert aufgerufen wird, um die Möglichkeit zu beseitigen, dass ein Wert als Objekt behandelt werden kann:

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

Implementierung

Referenzzählung

AutoHotkey verwendet einen einfachen Referenzzählungsmechanismus, um automatisch Ressourcen freizugeben, falls ein Objekt nicht länger in einem Skript benötigt bzw. verwiesen wird. Skript-Autoren sollten diesen Mechanismus nicht explizit aufrufen, es sei denn, sie müssen sich direkt mit unverwalteten Objekt-Pointern befassen.

In AutoHotkey v1.1 werden temporäre Referenzen, 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 die temporäre Referenz, zurückgegeben von {}, direkt nach dem Auswerten des Adresse-von-Operators freigegeben wird.

Um eine Aktion nach Freigabe der letzten Referenz zu einem Objekt durchzuführen, implementieren Sie die __Delete-Metafunktion.

Bekannte Einschränkungen:

Obwohl das Betriebssystem den vom Objekt benutzten Speicher zurückfordert, sobald das Programm beendet wird, kann __Delete erst aufgerufen werden, wenn alle Referenzen zum Objekt freigegeben worden sind. Das kann wichtig sein, wenn andere Ressourcen freigegeben werden, die nicht automatisch vom Betriebssystem zurückgefordert werden, wie zum Beispiel temporäre Dateien.

Objekt-Pointer

In einigen seltenen Fällen ist es eventuell erforderlich, dass ein Objekt via DllCall() an einen externen Code übergeben werden muss, oder dass ein Objekt in eine binäre Datenstruktur gespeichert werden muss, damit es später abgerufen werden kann. Die Adresse eines Objekts kann via Adresse := &Objekt abgerufen werden; dies würde allerdings effektiv zwei Referenzen zum Objekt erzeugen, während das Programm selbst nur die eine Referenz in Objekt kennt. Das Objekt wird gelöscht, sobald die letzte bekannte Referenz zum Objekt freigegeben wird. Demzufolge muss das Skript das Objekt darüber informieren, dass es eine Referenz erhalten hat. Es gibt zwei Wege, um das zu erreichen:

; Methode #1: Referenzanzahl explizit um 1 erhöhen.
Adresse := &Objekt
ObjAddRef(Adresse)

; Methode #2: Object() nutzen, das die Referenzanzahl um 1 erhöht und eine Adresse zurückgibt.
Adresse := Object(Objekt)

Mit dieser Funktion ist es auch möglich, eine Adresse wieder in eine Referenz zurückzuverwandeln:

Objekt := Object(Adresse)

In jedem Fall muss das Objekt informiert werden, wenn das Skript mit dieser Referenz fertig ist:

; Verringert die Referenzanzahl des Objekts um 1, damit es freigegeben werden kann:
ObjRelease(Adresse)

Generell sollte jede neue Kopie einer Objektadresse als separate Referenz zum Objekt behandelt werden, demzufolge sollte das Skript ObjAddRef() aufrufen, wenn es eine Kopie erhält, und sofort ObjRelease() aufrufen, bevor es eine verliert. Zum Beispiel sollte immer ObjAddRef() aufgerufen werden, wenn eine Adresse mit so etwas wie x := Adresse kopiert wird. Ebenso sollte das Skript ObjRelease() aufrufen, wenn es mit x fertig ist (oder dabei ist den Wert von x zu überschreiben).

Beachten Sie, 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.