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. So ein Verweis kann in eine andere Variable gespeichert werden (z. B. mit x := y), um beide Variablen auf das gleiche Objekt verweisen zu lassen.

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()

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-Keys vorhanden sind, wird MaxIndex eine leere Zeichenkette und Length eine 0 zurückgeben. 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)

Bei der {Key:Value}-Schreibweise muss der Key, sofern dieser nur aus Wortzeichen besteht, nicht in Anführungszeichen gesetzt werden. Der Key kann ein beliebiger Ausdruck sein, aber um eine Variable als Key zu benutzen, muss sie von runden Klammern umschlossen sein. 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 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

Bei allen Objekttypen kann die Schreibweise Objekt.DirektGeschriebenerKey benutzt werden, um auf eine Eigenschaft, ein Array oder eine Methode zuzugreifen. Hierbei ist DirektGeschriebenerKey ein Identifikator oder Integer und Objekt ein beliebiger Ausdruck. Identifikatoren sind Zeichenketten ohne Anführungszeichen, die aus alphanumerischen Zeichen, Unterstrich und seit [v1.1.09] aus Nicht-ASCII-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(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.

Außerdem wird ein Verweis, der 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 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, entfernt man 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 Objektverweise 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 Meta-Funktionen 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-Meta-Funktion 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.

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+]

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 Performance zu verbessern, kann der Verweis dieser Funktion in eine Variable gespeichert und später wiederverwendet werden:

Funk := Func("MeineFunk")

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

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

Weitere Eigenschaften von Funktionsverweisen finden Sie unter Func-Objekt.

Mehrdimensionale Arrays

AutoHotkey unterstützt "mehrdimensionale" Arrays, indem Arrays in andere Arrays gespeichert werden. Zum Beispiel repräsentieren die Reihen einer Tabelle die Felder eines Arrays, während die Felder selbst die Spalten der jeweiligen Reihe in Form eines Arrays enthalten. 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 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 Verweise 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 indirekter Parameter:
Loop 2
    array[A_Index]()

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

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

Benutzerdefinierte Objekte

Objekte, die das Skript erstellt hat, 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. 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 Base-Objekt mithilfe von Meta-Funktionen das Standardverhalten von jedem abgeleiteten Objekt überschreiben.

Base-Objekte sind ganz normale Objekte, die wie folgt 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 diesem Objekt 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, um alle Eigenschaften und Methoden, die das Objekt geerbt hat, nachhaltig zu ersetzen.

Prototypen

Prototypen oder base-Objekte werden wie jedes andere Objekt auch konstruiert und manipuliert. 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 eines Funktionsverweises.
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 Verweis) in das Objekt gespeichert wird (statt von einem Base-Objekt 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 ist ein Prototyp oder base, 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 einen Verweis auf anderesObj statt auf obj.

Klassen [v1.1.00+]

Eine "Klasse" ist im Grunde eine Gruppe oder Kategorie von Dingen, die Eigenschaften oder Attribute gemeinsam nutzen. Da ein Base- oder Prototyp-Objekt 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 Base-Objekte mithilfe des Schlüsselworts "class" definiert werden können:

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

    class VerschachtelteKlasse
    {
        ...
    }

    Methode()
    {
        ...
    }

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

Dieses Beispiel konstruiert, nachdem das Skript geladen wurde, ein Objekt und speichert es in die globale (oder seit v1.1.05 in die superglobale) Variable Klassenname. Vor v1.1.05 konnte diese Klasse innerhalb einer Funktion nur verwiesen werden, wenn eine Deklaration wie global Klassenname angegeben wurde, es sei denn, die Funktion war global-behandelnd. 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.

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 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 (sogar innerhalb einer Methode), gibt man immer das Zielobjekt an; 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 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 in Ausdruck wird als global angesehen.

Um einer Klassenvariable etwas zuweisen zu können, gibt man immer das Klassenobjekt an; zum Beispiel Klassenname.KlassenVar := Wert. Wenn ein Objekt x von Klassenname abgeleitet wurde und wenn x selbst nicht den Key "KlassenVar" enthält, kann mit x.KlassenVar zudem der Wert von Klassenname.KlassenVar dynamisch abgerufen werden. Allerdings würde x.KlassenVar := y den Wert nicht in Klassenname, 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 Klassenname.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 Klassenname.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 einen Verweis auf ein von der Klasse abgeleitetes Objekt enthält. Allerdings kann dieser Parameter 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 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 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 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 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.

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 via eigene Base-Klasse definierte Eigenschaft zugreifen zu können. Wenn get oder set nicht definiert ist, kann es von einer Base-Klasse behandelt werden. Wenn set nicht definiert ist und nicht von einer Meta-Funktion oder Base-Klasse 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).

Meta-Funktionen 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

Immer wenn ein abgeleitetes Objekt mit dem Schlüsselwort new erstellt wird [benötigt v1.1.00+], wird die via Base-Objekt definierte __New-Methode 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 einem obj.key beispielsweise keinen Wert zugewiesen wurde, wird die __Get-Meta-Funktion aufgerufen. obj.key := value veranlasst hingegen den Aufruf von __Set und obj.key() den Aufruf von __Call. Diese Meta-Funktionen (oder Methoden) sollten in obj.base, obj.base.base oder so ähnlich definiert sein.

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, verhält sie sich so, 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 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 erstellt werden, das Anfragen über das Netzwerk (oder über einen beliebig anderen Kanal) sendet, um Eigenum sich wie Funktionen zu verhalten.schaften anzufordern. 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). Meta-Funktionen 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 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 unbrauchbar machen.
    }

    __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 unbrauchbar machen.
    }

    ; 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, um den Code unter den Eigenschaften aufteilen zu lassen, indem sie alle eine zentrale Methode aufrufen. 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 geändert werden können, um sie wie Funktionen verhalten zu lassen.

Ein Funktionsobjekt kann sich auch wie eine Meta-Funktion 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 Meta-Funktionen sein können, 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 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 kein Base 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 Key-Parameter vorhanden sind. Erfolgt die oben genannte Zuweisung, findet folgendes statt:

Standard-Base-Objekt

Wenn ein Nicht-Objekt-Wert mit der Objektsyntax benutzt wird, wird das Standard-Base-Objekt 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 das Standard-Base kann nur erfolgen, wenn .base mit irgendeinem Nicht-Objekt-Wert verwendet wird; zum Beispiel "".base. Obwohl das Standard-Base nicht wie in "".base := Object() gesetzt werden kann, kann es selbst ein Base 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-Meta-Funktion ü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 Nicht-Objekt-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 Nicht-Objekt-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 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.

Um eine Aktion nach Freigabe des letzten Objektverweises durchzuführen, implementiert man die __Delete-Meta-Funktion.

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 Verweise auf das 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 Verweise auf das Objekt erzeugen, während das Programm selbst nur den einen Verweis in Objekt kennt. Das Objekt wird gelöscht, sobald der letzte bekannte Verweis auf das Objekt freigegeben wird. 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 ist es auch möglich, eine Adresse wieder in ein Verweis zurückzuverwandeln:

Objekt := Object(Adresse)

In jedem Fall muss das Objekt informiert werden, wenn das Skript 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 separater Objektverweis 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.