Objekte

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

Verwandte Themen:

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

Ergebnis := IsObject(Ausdruck)

Eine Liste von Standardobjekttypen finden Sie unter Objekttypen in der Seitenleiste der Dokumentation. Es gibt drei Grundtypen:

Objekte werden ab [AHK_L 31] unterstützt, einige Features erfordern jedoch 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 via InsertAt-Methode:

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

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

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

Entfernen eines Elements via RemoveAt-Methode:

EntfernterWert := Array.RemoveAt(Index)

Entfernen eines Elements via Pop-Methode:

EntfernterWert := Array.Pop()

MinIndex und MaxIndex/Length geben den niedrigsten bzw. höchsten Index eines nicht-leeren Arrays zurück. Da der niedrigste Index fast immer 1 ist, gibt MaxIndex üblicherweise die Anzahl der Elemente zurück. Wenn jedoch keine Integerschlüssel vorhanden sind, gibt MaxIndex eine leere Zeichenkette zurück, während Length 0 zurückgibt. Um die Elemente eines Arrays einzeln durchzugehen, verwenden Sie entweder A_Index oder eine For-Schleife. Zum Beispiel:

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

; Von 1 bis zum Ende des Arrays iterieren:
Loop % Array.Length()
    MsgBox % Array[A_Index]

; Den Inhalt des Arrays enumerieren:
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, wobei jeder Schlüssel mit einem Wert verbunden ist. 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)

Wenn die {Schlüssel:Wert}-Schreibweise verwendet wird, sind die Anführungszeichen für Schlüssel optional, solange die Schlüssel nur aus Wortzeichen bestehen. Der Schlüssel kann ein beliebiger Ausdruck sein, aber um eine Variable als Schlüssel zu verwenden, muss sie in runde Klammern gesetzt werden. Zum Beispiel wären sowohl {(SchlüsselVar): Wert} als auch {GetKey(): Wert} gültig.

Abrufen eines Elements:

Wert := Array[Schlüssel]

Zuweisen eines Elements:

Array[Schlüssel] := Wert

Entfernen eines Elements via 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 sein - z.B. enthält {1: "a", 1000: "b"} nur zwei Schlüssel-Wert-Paare, nicht 1000.

In AutoHotkey v1.x sind einfache Arrays und assoziative Arrays dasselbe. Die Behandlung von [] als einfaches lineares Array dient jedoch der Übersichtlichkeit und erhöht die Chance, dass Ihr Skript auch mit einer zukünftigen Version von AutoHotkey funktioniert, die möglicherweise zwischen einfachen Arrays und assoziativen Arrays unterscheidet.

Objekte [AHK_L 31+]

Für alle Objekttypen kann die Schreibweise Objekt.DirektGeschriebenerSchlüssel verwendet werden, um auf eine Eigenschaft, ein Array oder eine Methode zuzugreifen, wobei DirektGeschriebenerSchlüssel ein Identifikator oder Integer und Objekt ein beliebiger Ausdruck ist. Identifikatoren sind anführungszeichenlose Zeichenketten, die aus alphanumerischen Zeichen, Unterstrichen und in [v1.1.09+] aus ASCII-fremden Zeichen bestehen können. Zum Beispiel ist match.Pos äquivalent zu match["Pos"], während arr.1 äquivalent zu arr[1] ist. Nach dem Punkt darf kein Leerzeichen stehen.

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 generierten 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:

Objekte freigeben

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, sobald dieser Variable ein anderer Wert zugewiesen wird. Zum Beispiel:

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

Gleichermaßen wird eine Referenz, die in einem Feld eines anderen Objekts gespeichert ist, freigegeben, wenn dieses Feld einen anderen Wert zugewiesen bekommt oder aus dem Objekt entfernt wird. Dies gilt auch für Arrays, weil sie praktisch dasselbe sind wie Objekte.

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 z.B. x.child auf y und y.parent auf x verweist, genügt es nicht, x und y zu leeren, weil das Parent-Objekt noch eine Referenz zum Child-Objekt enthält, und umgekehrt. Um diese Situation zu lösen, entfernen Sie den Zirkelbezug.

x := {}, y := {}             ; Zwei Objekte erstellen.
x.child := y, y.parent := x  ; Einen Zirkelbezug erstellen.

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

Eine ausführlichere Verwendung und Informationen finden Sie unter Referenzzählung.

Bemerkungen

Syntax

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

Außerdem können Objektreferenzen selbst in Ausdrücken verwendet werden:

Wenn ein Objekt in einem Kontext verwendet wird, wo kein Objekt erwartet wird, wird es als leere Zeichenkette behandelt. Zum Beispiel bewirkt MsgBox %Objekt%, dass ein leeres Mitteilungsfenster angezeigt wird, und Objekt + 1, dass eine leere Zeichenkette zurückgegeben wird. Verlassen Sie sich nicht auf dieses Verhalten, da es sich in einer zukünftigen Version ändern kann.

Wenn unmittelbar nach einem Methodenaufruf ein Zuweisungsoperator folgt, entspricht dies dem Setzen einer Eigenschaft mit Parametern. Zum Beispiel sind die folgenden Zeilen funktionsgleich:

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 dieser ohne Wert übergeben wird. Beachten Sie, dass sich dies von x.(a) unterscheidet, das äquivalent zu x[""](a) ist. Wenn beim Aufruf eines COM-Objekts der Eigenschafts- oder Methodenname weggelassen wird, wird dessen "Standardelement" indirekt 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.(). Dies erfordert jedoch, dass der Funktionsname jedes Mal aufgelöst werden muss, was ineffizient ist, wenn die Funktion mehr als einmal aufgerufen wird. Zur Verbesserung der Performanz kann das Skript die Funktionsreferenz abrufen und für die spätere Verwendung speichern:

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 durch transparentes Speichern von Arrays in andere Arrays. Zum Beispiel kann eine Tabelle als ein Array von Zeilen dargestellt werden, wobei jede Zeile selbst ein Array von Spalten ist. 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 existiert, 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 Objekte, die per Skript erstellt wurden, 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")]

; Jede Funktion aufrufen und "foo" als Parameter übergeben:
Loop 2
    Array[A_Index].Call("foo")

; Jede Funktion aufrufen und implizit das Array selbst als Parameter übergeben:
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 Array nicht als Parameter übergeben, wenn Array[A_Index] einen Funktionsnamen anstelle einer Funktionsreferenz enthält. Wenn jedoch Array[A_Index] von Array.base[A_Index] geerbt wurde, wird Array als Parameter übergeben.

Benutzerdefinierte Objekte

Objekte, die per Skript erstellt wurden, müssen nicht zwingend eine vordefinierte Struktur haben. Stattdessen kann jedes Objekt Eigenschaften und Methoden von seinem Basisobjekt, auch "Prototyp" oder "Klasse" genannt, erben. Einem Objekt können jederzeit Eigenschaften und Methoden hinzugefügt (oder entfernt) werden; solche Änderungen wirken sich auf alle abgeleiteten Objekte aus. Für komplexere oder speziellere Fälle kann ein Basisobjekt das Standardverhalten von jedem abgeleiteten Objekt überschreiben, indem Metafunktionen definiert werden.

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:

; Ein Objekt erstellen.
obj := {}
; Einen Wert speichern.
obj.foo := "bar"
; Eine Methode durch Speichern einer Funktionsreferenz erstellen.
obj.test := Func("obj_test")
; Die Methode aufrufen.
obj.test()

obj_test(this) {
    MsgBox % this.foo
}

Beim Aufruf von obj.test() wird obj automatisch an den Anfang der Parameterliste gesetzt. Allerdings geschieht dies aus Gründen der Abwärtskompatibilität nicht, wenn eine Funktion direkt per Name (statt per Referenz) in das Objekt gespeichert wird (statt von einem Basisobjekt geerbt zu werden). Konventionsgemäß setzt sich der Name der Funktion aus dem "Typ" des Objekts und dem Namen der Methode zusammen.

Ein Objekt gilt als Prototyp oder Basis, wenn ein anderes Objekt von ihm ableitet:

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

anderesObj erbt in diesem Fall foo und test von obj. Diese Vererbung ist dynamisch, d.h. wenn obj.foo geändert wird, wird diese Änderung durch anderesObj.foo widergespiegelt. Wenn das Skript anderesObj.foo einen Wert zuweist, wird dieser Wert in anderesObj gespeichert; weitere Änderungen an obj.foo hätten keinen Effekt auf anderesObj.foo. Beim Aufruf von anderesObj.test() enthält dessen 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 gemeinsame Eigenschaften oder Attribute haben. 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 Basisobjekte einfach mit dem Schlüsselwort "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
        }
    }
}

Dies bewirkt, dass beim Laden des Skripts ein Objekt konstruiert und in die globale (oder in [v1.1.05+] in die superglobale) Variable KlasseName gespeichert wird. Um diese Klasse innerhalb einer Force-Local-Funktion (oder vor [v1.1.05] innerhalb einer Assume-Local- oder Assume-Static-Funktion) zu referenzieren, ist eine Deklaration wie global KlasseName erforderlich. Wenn extends BasisklasseName vorhanden ist, muss BasisklasseName der vollständige Name einer anderen Klasse sein (in [v1.1.11+] spielt die Reihenfolge ihrer Definition keine Rolle mehr). Der vollständige Name jeder Klasse ist in Objekt.__Class gespeichert.

Da die Klasse via Variable referenziert wird, kann der Klassenname nicht verwendet werden, um im selben Kontext sowohl die Klasse zu referenzieren 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 kann verwendet werden, um eine Warnung anzuzeigen, wenn versucht wird, 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, aber ohne Angabe des Präfixes this. (nur direkt im Körper der Klasse):

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, der aber nicht vom Skript verwendet werden sollte. Die __New()-Methode wird aufgerufen, nachdem alle Deklarationen dieser Art (auch solche, die in Basisklassen definiert wurden) ausgewertet wurden. Ausdruck kann mit this auf andere Instanzvariablen und Methoden zugreifen, aber alle anderen Variablenreferenzen werden als global betrachtet.

Um auf eine Instanzvariable zuzugreifen (auch innerhalb einer Methode), geben Sie immer das Zielobjekt an, z.B. this.InstanzVar.

[v1.1.08+]: Deklarationen wie x.y := z werden ebenfalls unterstützt, sofern vorher x in dieser Klasse deklariert wurde. Zum Beispiel bewirkt x := {}, x.y := 42, dass x deklariert und außerdem this.x.y initialisiert wird.

Statische bzw. Klassenvariablen [v1.1.00.01+]

Statische bzw. Klassenvariablen gehören nur der Klasse selbst, können aber von abgeleiteten Objekten (einschließlich Unterklassen) geerbt werden. Solche Variablen müssen mit dem Schlüsselwort "static" deklariert werden:

static KlasseVar := Ausdruck

Statische Deklarationen werden nur einmal ausgewertet, vor dem automatischen Ausführungsbereich, in der Reihenfolge ihres Erscheinens im Skript. Jede Deklaration speichert einen Wert in das Klassenobjekt. Alle Variablenreferenzen in Ausdruck werden als global betrachtet.

Um einer Klassenvariable etwas zuzuweisen, geben Sie immer das Klassenobjekt an, z.B. KlasseName.KlasseVar := Wert. Wenn ein Objekt x von KlasseName abgeleitet wurde und wenn x selbst nicht den Schlüssel "KlasseVar" enthält, kann auch x.KlasseVar verwendet werden, um dynamisch den Wert von KlasseName.KlasseVar abzurufen. Allerdings würde x.KlasseVar := y den Wert in x speichern, nicht in KlasseName.

[v1.1.08+]: Deklarationen wie static x.y := z werden ebenfalls unterstützt, sofern vorher x in dieser Klasse deklariert wurde. Zum Beispiel bewirkt static x := {}, x.y := 42, dass x deklariert und außerdem KlasseName.x.y initialisiert wird.

Verschachtelte Klassen

Verschachtelte Klassendefinitionen ermöglichen es, ein Klassenobjekt innerhalb eines anderen Klassenobjekts zu speichern, anstatt eine separate globale Variable zu verwenden. Im obigen Beispiel konstruiert class VerschachtelteKlasse ein Objekt und speichert es in KlasseName.VerschachtelteKlasse. Unterklassen können VerschachtelteKlasse erben oder mit ihrer eigenen verschachtelten Klasse überschreiben (in diesem Fall kann mit new this.VerschachtelteKlasse die entsprechende 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 Pseudoschlüsselwort base verwendet werden, um auf Superklassenversionen von Methoden oder Eigenschaften zuzugreifen, die in einer abgeleiteten Klasse überschrieben werden. Zum Beispiel würde base.Methode() in der oben definierten Klasse eine Version von Methode aufrufen, die durch BasisklasseName definiert ist. Metafunktionen werden nicht aufgerufen; andernfalls verhält sich base.Methode() wie BaseKlasseName.Methode.Call(this). Das heißt,

base hat nur eine besondere Bedeutung, wenn dahinter ein Punkt . oder eine eckige Klammer [] steht, daher wird so etwas wie obj := base, obj.Methode() nicht funktionieren. Skripte können das spezielle Verhalten von base deaktivieren, indem sie diesem einen nicht-leeren Wert zuweisen; dies wird jedoch nicht empfohlen. Da die Variable base leer sein muss, kann die Leistung beeinträchtigt sein, wenn das Skript ohne #NoEnv verwendet wird.

Eigenschaften [v1.1.16+]

Eigenschaftsdefinitionen ermöglichen die Ausführung einer Methode, wann immer das Skript einen bestimmten Schlüssel abruft oder setzt.

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

Eigenschaft ist der Name der Eigenschaft, der für ihren indirekten Aufruf verwendet wird. Zum Beispiel bewirkt obj.Eigenschaft, dass get aufgerufen wird, und obj.Eigenschaft := Wert, dass set aufgerufen wird. Innerhalb von get oder set bezieht sich this auf das Objekt, das gerade indirekt aufgerufen wird. Innerhalb von set enthält value den Wert, der gerade zugewiesen wird.

Parameter können übergeben werden, indem sie in eckige Klammern rechts vom Eigenschaftsnamen gesetzt werden, sowohl bei der Definition der Eigenschaft als auch bei ihrem Aufruf. Abgesehen von der Verwendung eckiger Klammern werden die Parameter von Eigenschaften genauso definiert wie die Parameter von Methoden - optionale, variadische und ByRef-Parameter werden unterstützt.

Der Rückgabewert von get oder set ist das Ergebnis eines Teilausdrucks, der die Eigenschaft indirekt aufgerufen hat. Zum Beispiel speichert Wert := obj.Eigenschaft := 42 den Rückgabewert von set in Wert.

Jede Klasse kann eine oder beide Hälften einer Eigenschaft definieren. Wenn eine Klasse eine Eigenschaft überschreibt, kann sie base.Eigenschaft verwenden, um auf die von ihrer Basisklasse definierte Eigenschaft zuzugreifen. 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, speichert die Zuweisung eines Wertes diesen in das Objekt, wodurch die Eigenschaft effektiv deaktiviert wird.

Intern sind get und set zwei separate Methoden, die keine Variablen gemeinsam nutzen können (es sei denn, sie werden in this gespeichert).

Metafunktionen bieten eine größere Auswahl an Möglichkeiten für den kontrollierten Zugriff auf Eigenschaften und Methoden eines Objekts, sind jedoch komplizierter und fehleranfälliger.

Konstruktion und Destruktion

Jedes Mal, wenn ein abgeleitetes Objekt mit dem Schlüsselwort new in [v1.1.00+] erstellt wird, wird die von seinem Basisobjekt definierte __New-Methode aufgerufen. Diese Methode kann Parameter akzeptieren, das Objekt initialisieren und das Ergebnis des new-Operators durch Rückgabe eines Wertes überschreiben. Bei Zerstörung des Objekts 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 % "Neues GMem von " aGröße " Bytes auf Adresse " this.ptr "."
        return this  ; Diese Zeile kann weggelassen werden, wenn der Operator 'new' verwendet wird.
    }

    __Delete()
    {
        MsgBox % "GMem auf Adresse " this.ptr " löschen."
        DllCall("GlobalFree", "Ptr", this.ptr)
    }
}

__Delete wird nicht für Objekte aufgerufen, die den Schlüssel "__Class" haben. Klassenobjekte haben diesen Schlüssel standardmäßig.

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

[v1.1.28+]: Wenn ein Ausnahme- oder Laufzeitfehler bei der Ausführung von __Delete ausgelöst und nicht innerhalb von __Delete behandelt wird, verhält es sich so, als wäre __Delete von einem neuen Thread aufgerufen worden. Das heißt, dass ein Fehlerdialogfenster angezeigt wird und __Delete zurückkehrt, aber der Thread nicht beendet wird (es sei denn, er wurde bereits beendet). Vor v1.1.28 führten unbehandelte Ausnahmen zu inkonsistentem Verhalten.

Wenn das Skript auf irgendeine Weise direkt terminiert wird, z.B. über das Tray-Menü, ExitApp oder Exit (wenn das Skript nicht persistent ist), bekommen 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 dazu führt, dass __Delete nicht aufgerufen wird.

Bei Beendigung des Skripts 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 noch 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 z.B. obj.Schlüssel keinen Wert zugewiesen bekommen hat, ruft es indirekt die __Get-Metafunktion auf. obj.Schlüssel := Wert bewirkt dagegen den indirekten Aufruf von __Set und obj.Schlüssel() den indirekten Aufruf von __Call. Diese Metafunktionen (oder Methoden) müssten in obj.base, obj.base.base oder ähnlichem definiert werden.

Metafunktionen werden in der Regel wie Methoden definiert, folgen aber nicht den gleichen Regeln (es sei denn, sie werden explizit vom Skript aufgerufen). Sie müssen in einem Basisobjekt definiert werden; eine 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 werden, verhalten sich aber ansonsten wie Methoden.

Hinweis: AutoHotkey v2 ersetzt Metafunktionen mit konventionelleren Methoden.

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

Wenn eine Metafunktion einen passenden Schlüssel in das Objekt speichert, aber nicht via return zurückkehrt, verhält sie sich so, als wäre der Schlüssel von Anfang an im Objekt vorhanden gewesen. Ein Beispiel zur Verwendung von __Set finden Sie unter Mehrdimensionale Arrays bei Unterklassen.

Wenn die Operation immer noch nicht behandelt wurde, prüfen, ob es sich um eine interne Methode oder Eigenschaft handelt:

Wenn die Operation immer noch nicht behandelt wurde,

Bekannte Einschränkung:

Dynamische Eigenschaften

Die Eigenschaftssyntax ermöglicht die Definition von Eigenschaften, die bei jeder Auswertung einen Wert ermitteln, allerdings muss jede Eigenschaft im Voraus bekannt und im Skript einzeln definiert sein. 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. Selbst wenn der Name jeder Eigenschaft im Voraus bekannt wäre, würde es keinen Sinn machen, jede Eigenschaft einzeln in der Proxy-Klasse zu definieren, da jede Eigenschaft dasselbe bewirkt (eine Netzwerkanfrage senden). Metafunktionen empfangen den Namen der Eigenschaft als Parameter und sind daher eine gute Lösung für dieses Problem.

Eine andere Verwendung von __Get und __Set ist die Implementierung einer Reihe von verwandten Eigenschaften, die sich Code teilen. Im folgenden Beispiel werden sie verwendet, um ein "Farbe"-Objekt mit R, G, B und RGB-Eigenschaften zu implementieren, wobei 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]  ; Die Anzahl der zu verschiebenden Bits abrufen.
        if (shift != "")  ; Ist es eine bekannte Eigenschaft?
            return (this.RGB >> shift) & 0xff
        ; HINWEIS: Ein 'return' würde hier this.RGB unbrauchbar machen.
    }

    __Set(aName, aWert)
    {
        if ((shift := Farbe.Shift[aName]) != "")
        {
            aWert &= 255  ; Auf eine geeignete Länge kürzen.

            ; Den neuen RGB-Wert berechnen und speichern.
            this.RGB := (aWert << shift) | (this.RGB & ~(0xff << shift))

            ; 'return' ist notwendig, um anzugeben, dass kein neues Schlüssel-Wert-Paar erstellt werden soll.
            ; Dies definiert auch, was in das 'x' in 'x := clr[name] := val' gespeichert wird:
            return aWert
        }
        ; HINWEIS: Ein 'return' würde hier 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 jedoch stattdessen die Eigenschaftssyntax verwenden können, bei der der Code gemeinsam genutzt wird, indem jede Eigenschaft einfach eine zentrale Methode aufruft. Metafunktionen sollten, wenn möglich, vermieden werden, da ein hohes Risiko besteht, dass sie falsch verwendet werden (siehe die roten Hinweise oben).

Objekte als Funktionen

Eine Übersicht über die Erstellung von Objekten, die als Funktionen fungieren können, finden Sie unter Funktionsobjekte.

Ein Funktionsobjekt kann auch als Metafunktion fungieren, um z.B. dynamische Eigenschaften zu definieren, die denen im vorherigen Abschnitt ähneln. Obwohl es ratsam ist, stattdessen die Eigenschaftssyntax zu verwenden, zeigt das folgende Beispiel das Potenzial von Metafunktionen zur Implementierung neuer Konzepte oder Verhaltensmuster, oder die Änderung der Struktur des Skripts.

; 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*)
    {
        ; Wenn dieses Eigenschaften-Objekt eine Definition für diese Teileigenschaft enthält, diese aufrufen.
        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 Multi-Parameter-Zuweisung wie Tabelle[x, y] := Inhalt implizit dazu führt, dass ein neues Objekt erstellt wird, hat das neue Objekt üblicherweise keine Basis und daher keine benutzerdefinierten Methoden oder spezielle Verhaltensweisen. Mit __Set können solche Objekte wie folgt initialisiert werden.

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

; Wert zuweisen, was implizit x_Setter aufruft, um Unterobjekte zu erstellen.
x[1,2,3] := "..."

; Wert abrufen und Beispielmethode aufrufen.
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. Wenn die obige Zuweisung erfolgt, geschieht folgendes:

Standardbasisobjekt

Wenn ein Nicht-Objekt-Wert mit der Objektsyntax verwendet wird, wird indirekt das Standardbasisobjekt aufgerufen. Dies kann verwendet werden, um das Skript zu debuggen oder um global objektähnliche Verhaltensweisen für Zeichenketten, Zahlen und/oder Variablen zu definieren. Um auf die Standardbasis zuzugreifen, verwenden Sie .base mit einem Nicht-Objekt-Wert, z.B. "".base. Obwohl die Standardbasis nicht gesetzt werden kann, wie in "".base := Object(), kann die Standardbasis selbst eine Basis haben, wie in "".base.base := Object().

Automatische Variableninitialisierung

Wenn eine leere Variable als Ziel einer Set-Operation verwendet wird, wird sie direkt an die __Set-Metafunktion übergeben, was ihr die Möglichkeit gibt, ein neues Objekt in die Variable einzufügen. Aus Platzgründen unterstützt dieses Beispiel nicht mehr als einen Parameter, was aber mit einer variadischen Funktion möglich wäre.

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

Debugging

Wenn es unerwünscht ist, die Behandlung eines Wertes als Objekt zu erlauben, kann eine Warnung angezeigt werden, wann immer ein Nicht-Objekt-Wert indirekt 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äß indirekt aufgerufen.`n`nSpeziell: %nonobj%
}

Implementierung

Referenzzählung

AutoHotkey verwendet einen einfachen Referenzzählmechanismus, um belegte Ressourcen eines Objekts automatisch freizugeben, wenn es nicht länger im Skript referenziert wird. Skriptautoren sollten diesen Mechanismus nicht explizit aufrufen, es sei denn, sie arbeiten direkt mit unverwalteten Objektpointern.

In AutoHotkey v1.1 werden temporäre Referenzen, die innerhalb eines Ausdrucks erstellt (aber nirgendwo gespeichert) werden, sofort nach ihrer Verwendung freigegeben. Zum Beispiel übergibt Fn(&{}) eine ungültige Adresse an die Funktion, da die temporäre Referenz, zurückgegeben von {}, sofort nach der Auswertung des Adresse-von-Operators freigegeben wird.

Um Code beim Freigeben der letzten Referenz zu einem Objekt auszuführen, implementieren Sie die __Delete-Metafunktion.

Bekannte Einschränkungen:

Obwohl das Betriebssystem den vom Objekt belegten Speicher bei Beendigung des Programms zurückfordern wird, wird __Delete erst aufgerufen, wenn alle Referenzen zum Objekt freigegeben wurden. Dies kann wichtig sein, wenn es andere Ressourcen freigibt, die nicht automatisch vom Betriebssystem zurückgefordert werden, wie z.B. temporäre Dateien.

Objektpointer

In seltenen Fällen kann es erforderlich sein, dass ein Objekt via DllCall() an einen externen Code übergeben werden muss oder dass ein Objekt in eine binäre Datenstruktur für späteren Gebrauch gespeichert werden muss. Die Adresse eines Objekts kann via Adresse := &Objekt abgerufen werden, allerdings werden dadurch effektiv zwei Referenzen zum Objekt erzeugt, 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. Folglich muss das Skript dem Objekt mitteilen, 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() verwenden, 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 umzuwandeln:

Objekt := Object(Adresse)

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

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

Generell sollte jede neue Kopie einer Objektadresse als separate Referenz zum Objekt behandelt werden, d.h. das Skript sollte ObjAddRef() aufrufen, wenn es eine Kopie erhält, und sofort ObjRelease() aufrufen, bevor es eine Kopie 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 auch auf Objekte angewendet werden kann, die nicht von ihr selbst erzeugt wurden, wie z.B. COM-Objekt-Wrapper oder File-Objekte.