オブジェクトは、いくつかのプロパティとメソッドを組み合わせたものである。
関連トピック:
IsObjectは、値がオブジェクトであるかどうかを判定するために使用できます:
Result := IsObject(expression)
標準的なオブジェクト・タイプのリストについては、組み込みクラスを参照のこと。基本的には2つのタイプがある:
配列を作成する:
MyArray := [Item1, Item2, ..., ItemN] MyArray := Array(Item1, Item2, ..., ItemN)
項目(または配列要素)を取得します:
Value := MyArray[Index]
項目の値を変更する(Index
は1からLengthの間、または同等の逆インデックスでなければならない):
MyArray[Index] := Value
InsertAtメソッドを使用して、指定されたインデックスに1つ以上の項目を挿入します:
MyArray.InsertAt(Index, Value, Value2, ...)
Pushメソッドを使用して、1つまたは複数のアイテムを追加します:
MyArray.Push(Value, Value2, ...)
RemoveAtメソッドを使用して項目を削除します:
RemovedValue := MyArray.RemoveAt(Index)
Popメソッドを使って最後の項目を削除する:
RemovedValue := MyArray.Pop()
Lengthは、配列の項目数を返します。配列の中身をループするには、インデックスを使う方法と、Forループを使う方法がある。事例:
MyArray := ["one", "two", "three"] ; Iterate from 1 to the end of the array: Loop MyArray.Length MsgBox MyArray[A_Index] ; Enumerate the array's contents: For index, value in MyArray MsgBox "Item " index " is '" value "'" ; Same thing again: For value in MyArray MsgBox "Item " A_Index " is '" value "'"
Mapまたは連想配列は、一意のキーと値のコレクションを含むオブジェクトで、各キーは1つの値と関連付けられています。キーは文字列、整数、オブジェクトのいずれでもよく、値はどのような型でもよい。連想配列は次のようにして作ることができる:
MyMap := Map("KeyA", ValueA, "KeyB", ValueB, ..., "KeyZ", ValueZ)
Value := MyMap[Key]
項目を割り当てます:
MyMap[Key] := Value
Deleteメソッドを使用して項目を削除します:
RemovedValue := MyMap.Delete(Key)
項目を列挙する:
MyMap := Map("ten", 10, "twenty", 20, "thirty", 30) For key, value in MyMap MsgBox key ' = ' value
オブジェクトはプロパティと アイテム(配列要素など)を持つことができる。項目へのアクセスは、前のセクションで示したように[]
を使用する。プロパティは通常、ドットの後に識別子(単なる名前)を書いてアクセスする。メソッドは、呼び出すことができるプロパティである。
例:
文字どおりPropertyという名前のプロパティを取得または設定します:
Value := MyObject.Property
MyObject.Property := Value
式または変数の評価によって名前が決定されるプロパティを取得または設定します:
Value := MyObject.%Expression%
MyObject.%Expression% := Value
文字通りMethodという名前のプロパティ/メソッドを呼び出す:
ReturnValue := MyObject.Method(Parameters)
式または変数の評価によって名前が決定されるプロパティ/メソッドを呼び出す:
ReturnValue := MyObject.%Expression%(Parameters)
プロパティの取得や割り当ての際にパラメータを受け付けることがある:
Value := MyObject.Property[Parameters] MyObject.Property[Parameters] := Value
オブジェクトはインデックスをサポートすることもある:MyArray[Index]
は、実際にはMyArray
の__Itemプロパティを呼び出し、Index
をパラメータとして渡す。
オブジェクト・リテラルを式の中で使うと、即席のオブジェクトを作ることができる。オブジェクト・リテラルは、カンマで区切られた名前と値のペアのリストを中括弧({}
)で囲んだものである。各ペアは、コロン(:
)で区切られたリテラル(引用符で囲まれていない)プロパティ名と値(サブ式)で構成されます。事例:
Coord := {X: 13, Y: 240}
これは等価だ:
Coord := Object() Coord.X := 13 Coord.Y := 240
各名前と値のペアは、値のプロパティを定義させるが、例外としてBaseを設定することができる(通常の代入と同じ制限がある)。
名前の置換は、式または変数を評価することによってプロパティ名を決定することができます。事例:
parts := StrSplit("key = value", "=", " ") pair := {%parts[1]%: parts[2]} MsgBox pair.key
スクリプトは明示的にオブジェクトを解放しない。オブジェクトへの最後の参照が解放されると、オブジェクトは自動的に解放される。変数に格納された参照は、その変数に他の値が代入されると自動的に解放される。事例:
obj := {} ; オブジェクトを生成します。 obj := "" ; 最後の参照を解放します。つまり、オブジェクトを破棄します。
同様に、プロパティや配列要素に格納された参照は、そのプロパティや配列要素に他の値が割り当てられたり、オブジェクトから削除されたりすると解放される。
arr := [{}] ; オブジェクトを含んだ配列を作ります。 arr[1] := {} ; 2番目のオブジェクトを生成します。黙示的に最初のオブジェクトを破棄します。 arr.RemoveAt(1) ; 2番目のオブジェクトを破棄します。
オブジェクトを解放する前に、オブジェクトへの参照をすべて解放しなければならないので、循環参照を含むオブジェクトは自動的に解放されない。例えば、x.child
が y
を参照し、y.parent
が x
を参照している場合、x
と y
をクリアするだけでは不十分である。この状況を解決するには、循環参照を削除する。
x := {}, y := {} ; Create two objects. x.child := y, y.parent := x ; Create a circular reference. y.parent := "" ; The circular reference must be removed before the objects can be freed. x := "", y := "" ; Without the above line, this would not free the objects.
より高度な使い方や詳細については、参照カウントを参照のこと。
「多次元」配列はサポートされていないが、スクリプトは複数の配列やマップを組み合わせることができる。事例:
grid := [[1,2,3], [4,5,6], [7,8,9]] MsgBox grid[1][3] ; 3 MsgBox grid[3][2] ; 8
カスタム・オブジェクトは、__Itemプロパティを定義することで、多次元サポートを実装することができます。事例:
class Array2D extends Array { __new(x, y) { this.Length := x * y this.Width := x this.Height := y } __Item[x, y] { get => super.Has(this.i[x, y]) ? super[this.i[x, y]] : false set => super[this.i[x, y]] := value } i[x, y] => this.Width * (y-1) + x } grid := Array2D(4, 3) grid[4, 1] := "#" grid[3, 2] := "#" grid[2, 2] := "#" grid[1, 3] := "#" gridtext := "" Loop grid.Height { y := A_Index Loop grid.Width { x := A_Index gridtext .= grid[x, y] || "-" } gridtext .= "`n" } MsgBox gridtext
本当のスクリプトは、エラーチェックを行い、列挙をサポートする__Enumのような他のメソッドをオーバーライドすべきである。
カスタム・オブジェクトを作成するには、一般的に2つの方法がある:
メタファンクションは、オブジェクトの振る舞いをさらにコントロールするために使うことができる。
注:このセクションでは、オブジェクトとは Objectクラスのインスタンスを指します。このセクションはCOMオブジェクトには適用されない。
プロパティとメソッド(呼び出し可能なプロパティ)は通常、いつでも新しいオブジェクトに追加できる。例えば、1つのプロパティと1つのメソッドを持つオブジェクトは次のように構築される:
; Create an object. thing := {} ; Store a value. thing.foo := "bar" ; Define a method. thing.test := thing_test ; Call the method. thing.test() thing_test(this) { MsgBox this.foo }
上記のオブジェクトはthing := {foo: "bar"}
でも同様に作成できる。{property:value}表記を使う場合、プロパティに引用符を使ってはならない。
thing.test()
が呼ばれると、thingは自動的にパラメータ・リストの先頭に挿入される。慣習上、関数はオブジェクトの「タイプ」とメソッド名を組み合わせて命名されるが、これは必須条件ではない。
上記の例では、testが定義された後に他の関数や値が割り当てられる可能性があり、その場合、元の関数は失われ、このプロパティを通じて呼び出すことはできない。別の方法として、以下のように読み取り専用のメソッドを定義することもできる:
thing.DefineProp 'test', {call: thing_test}
こちらも参照のこと:DefineProp
オブジェクトはプロトタイプベース。つまり、オブジェクト自体で定義されていないプロパティは、代わりにオブジェクトのベースで定義することができる。これは、委譲による継承または差分継承と呼ばれるもので、オブジェクトは、そのオブジェクトを異なるものにする部分のみを実装し、残りをベースに委譲することができるからである。
ベース・オブジェクトは一般にプロトタイプとも呼ばれるが、「クラスのプロトタイプ」はクラスのすべてのインスタンスがベースとするオブジェクトを意味し、「ベース」はインスタンスがベースとするオブジェクトを意味する。
AutoHotkeyのオブジェクトデザインは、主にJavaScriptとLuaの影響を受けており、C#も少し含まれています。JavaScriptのobj.__proto__
の代わりにobj.base
を使い、JavaScriptのfunc.prototype
の代わりにcls.Prototype
を使います。(クラス・オブジェクトはコンストラクタ関数の代わりに使われる。)
オブジェクトのベースは、そのタイプやクラスを識別するためにも使われる。例えば、x := []
はArray.Prototype
に基づくオブジェクトを作成する。これは、x is Array
であり、x.HasBase(Array.Prototype)
が真であり、type(x)
が "Array"を返すことを意味する。各クラスのPrototypeはベース・クラスのPrototypeに基づいているので、x.HasBase(Object.Prototype)
も真になる。
Objectのインスタンスや派生クラスはすべてベース・オブジェクトになることができるが、オブジェクトは同じネイティブ・タイプを持つオブジェクトのベースとしてのみ割り当てることができる。これは、組み込みメソッドが常にオブジェクトのネイティブ・タイプを識別し、正しいバイナリ構造を持つオブジェクトに対してのみ操作できるようにするためである。
ベース・オブジェクトは2つの異なる方法で定義できる:
ベース・オブジェクトは他のオブジェクトのベース・プロパティに割り当てることができますが、通常、オブジェクトのベースはオブジェクトが作成されるときに暗黙的に設定されます。
どんなオブジェクトでも、同じネイティブ・タイプを持つ他のオブジェクトのベースとして使うことができる。次の例は、アドホック(Ad Hoc)の前の例を基にしたものである(実行する前にこの2つを組み合わせてください):
other := {} other.base := thing other.test()
この場合、otherは thingからfooと testを継承する。この継承は動的なので、thing.foo
が変更されると、その変更はother.foo
に反映される。スクリプトがother.foo
に代入した場合、その値はotherに格納され、thing.foo
をさらに変更してもother.foo
には影響しません。other.test()
が呼び出されると、thisパラメータにはthingの代わりにotherへの参照が含まれる。
オブジェクト指向プログラミングでは、クラスはオブジェクトを作成するための拡張可能なプログラム・コード・テンプレートであり、状態の初期値(メンバ変数)や動作の実装(メンバ関数またはメソッド)を提供する。ウィキペディア
より一般的な言い方をすれば、クラスとは、何らかの特性や属性を共通に持つ物事の集合やカテゴリーのことである。AutoHotkeyでは、class
はそのクラスのインスタンスで共有されるプロパティ(および呼び出し可能なプロパティであるメソッド)を定義します。インスタンスとは、クラスからプロパティを継承したオブジェクトのことで、通常はそのクラスに属していることを識別することもできます(instance is ClassName
という表現など)。インスタンスは通常、ClassName()を呼び出して作成します。
オブジェクトは 動的でプロトタイプベースなので、各クラスは2つの部分で構成される:
static
キーワードを持たないすべてのプロパティとメソッドが含まれる。static
キーワードを持つすべてのプロパティとメソッド、およびすべてのネストされたクラスが含まれます。これらは特定のインスタンスには適用されず、クラス自体を名前で参照することで使用できる。以下に、クラス定義のほとんどの要素を示す:
class ClassName extends BaseClassName { InstanceVar := Expression static ClassVar := Expression class NestedClass { ... } Method() { ... } static Method() { ... } Property[Parameters] ; Use brackets only when parameters are present. { get { return value of property } set { Store or otherwise handle value } } ShortProperty { get => Expression which calculates property value set => Expression which stores or otherwise handles value } ShorterProperty => Expression which calculates property value }
スクリプトがロードされると、Classオブジェクトが構築され、ClassNameという名前のグローバル定数(読み取り専用変数)に格納されます。extends BaseClassName
がある場合、BaseClassNameは他のクラスの完全な名前でなければならない。各クラスの完全な名前はClassName.Prototype.__Class
に格納されます。
クラス自体は変数を通してアクセスされるため、クラス名を使用してクラスを参照し、同じコンテキストで別の変数(クラスのインスタンスを保持するなど)を作成することはできません。例えば、box := Box()
は、box
も Box
も同じものに解決されるため、機能しない。この方法でトップ・レベルの(入れ子になっていない)クラスを再割り当てしようとすると、ロード・タイム・エラーになります。
この文書では、「クラス」という単語は通常、class
キーワードで構築されたクラス・オブジェクトを意味する。
クラス定義には、変数宣言、メソッド定義、ネストされたクラス定義を含めることができます。
インスタンス変数とは、クラスの各インスタンスがそれ自身のコピーを持つ変数である。これらは宣言され、通常の代入と同じように振る舞いますが、this.
接頭辞は省略されます(クラス本体内でのみ):
InstanceVar := Expression
これらの宣言は、ClassName()でクラスの新しいインスタンスが生成されるたびに評価され、すべての基本クラス宣言が評価された後、__Newが呼ばれる前に評価されます。これは、super.__Init()
への呼び出しを含む__Initという名前のメソッドを自動的に作成し、そこに各宣言を挿入することで実現される。したがって、1つのクラス定義に__Initメソッドとインスタンス変数宣言の両方を含めることはできません。
Expressionは this
を介して他のインスタンス変数やメソッドにアクセスできる。グローバル変数は読み込むことはできるが、代入することはできない。式の中で追加の代入(または参照演算子の使用)を行うと、一般的に__Initメソッドのローカル変数が作成される。例えば、x := y := 1
とすると、this.x
とローカル変数y
が設定される(初期化子がすべて評価されると解放される)。
インスタンス変数にアクセスするには(メソッド内であっても)、例えば、this.InstanceVar
のようにします。
x.y := z
のような宣言も、x
が以前にこのクラスで定義されていればサポートされる。例えば、x := {}, x.y := 42
はx
を宣言し、this.x.y
も初期化する。
静的変数/クラス変数はクラスそのものに属しますが、その値はサブクラスに継承させることができます。インスタンス変数と同じように、staticキーワードを使って宣言する:
static ClassVar := Expression
これらの宣言は、クラスが初期化されるときに一度だけ評価される。この目的のために、__Initという静的メソッドが自動的に定義される。
各宣言は、クラス・オブジェクトをターゲットとして、通常のプロパティ割り当てとして動作します。式は、this
がクラス自身を参照することを除いて、インスタンス変数と同じ解釈をする。
例えば、ClassName.ClassVar := Value
のようにします。サブクラスがその名前のプロパティを所有していない場合、Subclass.ClassVar
を使用して値を取得することもできます。したがって、値がオブジェクトへの参照である場合、サブクラスはデフォルトでそのオブジェクトを共有します。ただし、Subclass.ClassVar := y
は、ClassNameではなくSubclassに値を格納します。
static x.y := z
のような宣言も、x
が以前にこのクラスで定義されていればサポートされる。例えば、static x := {}, x.y := 42
はx
を宣言し、ClassName.x.y
も初期化します。Prototypeは各クラスで暗黙的に定義されているため、static Prototype.sharedValue := 1
を使用すると、クラスのすべてのインスタンスに動的に継承される値を設定できます(インスタンス自体のプロパティによってシャドウされるまで)。
入れ子になったクラス定義では、クラス・オブジェクトを別のグローバル変数ではなく、外側のクラスの静的/クラス変数に関連付けることができます。上記の例では、class NestedClass
が Classオブジェクトを構築し、ClassName.NestedClass
に格納しています。サブクラスは、NestedClassを継承するか、独自のネスト・クラスでオーバーライドすることができます(この場合、WhichClass.NestedClass()
を使用して、適切なクラスをインスタンス化できます)。
class NestedClass { ... }
クラスを入れ子にすることは、外側のクラスとの特定の関係を意味するものではない。ネストされたクラスは自動的にインスタンス化されないし、ネストされたクラスのインスタンスは、スクリプトが明示的にその接続を行わない限り、アウター・クラスのインスタンスとの接続を持たない。
Each nested class definition produces a dynamic property with get and call accessor functions instead of a simple value property. This is to support the following behaviour (where class X contains the nested class Y):
X.Y()
does not pass X to X.Y.Call
and ultimately __New
, which would otherwise happen because this is the normal behaviour for function objects called as methods (which is how the nested class is being used here).X.Y := 1
はデフォルトでエラーとなります(プロパティは読み取り専用です)。メソッド定義は関数定義と同じように見える。各メソッド定義は、this
という名前の隠された第1パラメータを持つFuncを作成し、メソッドの呼び出しやその関数オブジェクトの取得に使用されるプロパティを定義する。
方法には2種類ある:
this
はクラスのインスタンスを参照する。static
を付けて定義します。これらはクラス・オブジェクト自体に付属していますが、サブクラスにも継承されます。従って、this
はクラス自身かサブクラスのどちらかを指します。以下のメソッド定義では、target.DefineProp('Method', {call: funcObj})
と同じ型のプロパティを作成する。デフォルトでは、target.Method
は funcObjを返し、target.Method
に代入しようとするとエラーがスローされます。これらのデフォルトは、プロパティを定義するかDefinePropを呼び出すことでオーバーライドできます。
Method() { ... }
ファット・アロー構文は、式を返す単一行のメソッドを定義するために使用できます:
Method() => Expression
メソッドやプロパティのゲッター/セッターの内部では、派生クラスでオーバーライドされているメソッドやプロパティのスーパークラスのバージョンにアクセスするために、this
の代わりにsuper
キーワードを使用することができます。例えば、上で定義したクラスのsuper.Method()
は、通常、BaseClassNameの中で定義されたバージョンのMethodを呼び出します。注:
super.Method()
は、たとえthis
がそのクラスのサブクラスや他のクラスから派生したものであっても、現在のメソッドの元の定義に関連付けられたクラスやプロトタイプオブジェクトのベースを常に呼び出します。super.Method()
は、暗黙のうちにthis
を最初の(隠し)パラメータとして渡す。super.Method()
は、(ClassName.Prototype.base.Method)(this)
とほぼ等価である(ただし、Methodがstaticの場合はPrototypeを含まない)。しかし、ClassName.Prototype
はロード時に解決される。キーワードsuper
の後には、以下の記号のいずれかをつけなければならない:.[(
super()
はsuper.call()
と等価である。
プロパティ定義は、単に値を格納したり返したりするのではなく、メソッドを呼び出す動的プロパティを作成します。
Property[Parameters] { get { return property value } set { Store or otherwise handle value } }
Propertyは単にプロパティの名前であり、このプロパティを呼び出すために使用される。例えば、obj.Property
は getを呼び、obj.Property := value
は setを呼びます。getまたはsetの中では、this
は呼び出されるオブジェクトを指す。セット内では、value
に代入される値が含まれる。
パラメータは、プロパティ名の右側に角括弧で囲んで定義することができ、同じように渡されます - ただし、パラメータが存在しない場合は省略する必要があります(下記参照)。角括弧を使用することを除けば、プロパティのパラメータはメソッドのパラメータと同じように定義され、オプショナル、ByRef、バリアディックパラメータに対応しています。
プロパティがパラメータ付きで呼び出され、何も定義されていない場合、パラメータは自動的にgetによって返されるオブジェクトの__Itemプロパティに転送される。例えば、this.Property[x]
は、(this.Property)[x]
またはy := this.Property, y[x]
と同じ効果を持つ。空括弧(this.Property[]
)は、常にPropertyの値の__Itemプロパティを呼び出しますが、this.Property[args*]
のようなバリアディック呼び出しは、パラメータの数がゼロでない場合にのみ、この効果を持ちます。
スタティック・プロパティは、プロパティ名の前にstatic
という個別のキーワードを付けて定義することができます。その場合、this
はクラスそのものかサブクラスを指す。
setの戻り値は無視される。例えば、val := obj.Property := 42
は、例外をスローするかスレッドを終了しない限り、プロパティが何をするかに関係なく、常にval := 42
を代入する。
各クラスはプロパティの片方または両方を定義できる。クラスがプロパティをオーバーライドする場合は super.Property
を使用して、ベース・クラスで定義されたプロパティにアクセスできます。GetまたはSetが定義されていない場合は、ベース・オブジェクトから継承することができる。Getが未定義の場合、プロパティはベースから継承した値を返すことができます。Setがこのオブジェクトとすべてのベースオブジェクトで未定義である場合(または継承された値プロパティによって隠されている場合)、プロパティを設定しようとすると例外がスローされます。
getと setの両方を持つプロパティ定義は、実際には2つの別々の関数を作成し、ローカル変数や静的変数、入れ子関数を共有することはありません。メソッドと同様、各関数にはthis
という隠しパラメータがあり、setには value
という2番目の隠しパラメータがある。明示的に定義されたパラメータはその後に来る。
プロパティ定義では、DefinePropと同じようにプロパティのgetと setのアクセサ関数を定義しますが、メソッド定義では呼び出しのアクセサ関数を定義します。どのクラスも、同じ名前のプロパティ定義とメソッド定義を含むことができる。呼び出しアクセッサ関数(メソッド)を持たないプロパティが呼び出された場合、getはパラメータなしで呼び出され、その結果はメソッドとして呼び出される。
ファット・アロー構文を使用すると、式を返すプロパティ・ゲッターまたはセッターを定義できます:
ShortProperty[Parameters] { get => Expression which calculates property value set => Expression which stores or otherwise handles value }
ゲッターだけを定義する場合、中括弧とget
は省略できる:
ShorterProperty[Parameters] => Expression which calculates property value
いずれの場合も、パラメータが定義されていない限り、角括弧は省略しなければならない。
__Enum(NumberOfVars)
Enumメソッドは、オブジェクトがforループに渡されるときに呼び出される。このメソッドは、配列要素など、オブジェクトに含まれる項目を返す列挙子を返す必要があります。未定義のままだと、列挙子と互換性のあるCallメソッドを持たない限り、オブジェクトを直接forループに渡すことはできない。
NumberOfVarsには、forループに渡される変数の数が入る。NumberOfVarsが2の場合、列挙者は項目のキーまたはインデックスを最初のパラメータに、値を2番目のパラメータに代入することが期待される。各キーまたはインデックスは、__Itemプロパティのパラメータとして受け入れられるべきである。これにより、DBGpベースのデバッガは、列挙器を呼び出して項目をリストアップした後、特定の項目を取得または設定できるようになる。
Itemプロパティは、インデックス演算子(配列構文)がオブジェクトと共に使用されるときに呼び出される。以下の例では、Envクラス自体でインデックス演算子を使用できるように、プロパティはstaticとして宣言されています。他の例としては、Array2Dを参照。
class Env { static __Item[name] { get => EnvGet(name) set => EnvSet(name, value) } } Env["PATH"] .= ";" A_ScriptDir ; Only affects this script and child processes. MsgBox Env["PATH"]
__Item
is effectively a default property name (if such a property has been defined):
object[params]
はobject.__Item[params]
と等価である。object[]
はobject.__Item
と等価である。事例:
obj := {} obj[] := Map() ; Equivalent to obj.__Item := Map() obj["base"] := 10 MsgBox obj.base = Object.prototype ; True MsgBox obj["base"] ; 10
注: obj.prop[]
のように明示的なプロパティ名が空括弧と組み合わされているときは、それは2つの別々の操作として扱われます:まずobj.prop
を取得し、次に結果のデフォルト・プロパティを呼び出します。これは言語構文の一部であり、オブジェクトに依存するものではない。
ClassName()のデフォルト実装によってオブジェクトが生成されるたびに、新しいオブジェクトの__New
メソッドが呼び出されます。ClassName()
に渡されたパラメータはすべて__New
に転送されるため、オブジェクトの初期内容や構築方法に影響を与える可能性があります。オブジェクトが破棄されると、__Delete
が呼ばれる。事例:
m1 := GMem(0, 10) m2 := {base: GMem.Prototype}, m2.__New(0, 30) ; Note: For general memory allocations, use Buffer() instead. class GMem { __New(aFlags, aSize) { this.ptr := DllCall("GlobalAlloc", "UInt", aFlags, "Ptr", aSize, "Ptr") if !this.ptr throw MemoryError() MsgBox "New GMem of " aSize " bytes at address " this.ptr "." } __Delete() { MsgBox "Delete GMem at address " this.ptr "." DllCall("GlobalFree", "Ptr", this.ptr) } }
__Deleteは、"__Class"というプロパティを所有するオブジェクトに対しては呼び出されません。プロトタイプ・オブジェクトはデフォルトでこのプロパティを持っている。
Deleteの実行中に例外または実行時エラーがスローされ、__Delete内で処理されない場合、__Deleteは新しいスレッドから呼び出されたかのように動作します。つまり、エラー・ダイアログが表示され、__Deleteが戻るが、スレッドは終了しない(すでに終了していた場合を除く)。
トレイメニューやExitAppなど、何らかの手段でスクリプトが直接終了した場合、まだ戻っていない関数はその機会を得られない。したがって、これらの関数のローカル変数によって参照されるオブジェクトは解放されないので、__Deleteは呼び出されません。Temporary references on the expression evaluation stack are also not released under such circumstances.
スクリプトが終了すると、グローバル変数と静的変数に含まれるオブジェクトは、実装で定義された任意の順序で自動的に解放されます。この処理中に__Deleteが呼ばれた場合、グローバル変数やスタティック変数はすでに解放されている可能性があるが、オブジェクト自身が含む参照はまだ有効である。したがって、__Deleteは完全に自己完結型とし、グローバル変数や静的変数に依存しないのがベストである。
各クラスは、そのクラスへの参照が初めて評価されるときに自動的に初期化される。例えば、MyClassがまだ初期化されていない場合、MyClass.MyProp
はプロパティが取得される前にクラスを初期化します。初期化は、2つの静的メソッドを呼び出すことからなる:__Init、__New。
static __Init
は、すべてのクラスに対して自動的に定義され、ベース・クラスが指定されていれば常にそのクラスへの参照から始まり、確実に初期化されます。静的/クラス変数と ネストされたクラスは、定義された順番に初期化されます。ただし、ネストされたクラスが前の変数やクラスの初期化中に参照された場合は除きます。
クラスがstatic __New
メソッドを定義または継承している場合、 __Init の直後に呼び出されます。__Newは、それが定義されているクラスに対して一度だけ呼び出されることに注意することが重要でありそして、定義されていないサブクラス(またはsuper.__New()
を呼び出すサブクラス)ではそれぞれ1回呼び出されるということです。これは、各サブクラスに対して共通の初期化タスクを実行したり、サブクラスを使用する前に何らかの方法で修正したりするために使用できます。
static __New
が派生クラスに対して作用することを意図していない場合は、this
の値をチェックすることで回避できる。場合によっては、this.DeleteProp('__New')
のように、メソッド自体を削除すれば十分かもしれません。しかし、__Newの最初の実行は、サブクラスが基本クラスに入れ子になっている場合や、静的/クラス変数の初期化中に参照される場合があります。
クラス定義は、クラスを参照する効果もある。言い換えると、スクリプトの起動中に実行がクラス定義に達すると、__Initと__Newが自動的に呼び出されます。しかし、return
や無限ループなど、実行がクラス定義に到達できない場合は、クラスが参照されている場合のみ初期化される。
一度自動初期化が開始されると、同じクラスで再び初期化が行われることはありません。これは、複数のクラスが互いに参照し合わない限り、一般的には問題にはなりません。例えば、以下の2つのクラスを考えてみよう。A
が最初に初期化されるときは、B.SharedArray
(A1)を評価すると、値を取得して返す前にB
が初期化されますが、A.SharedValue
(A3)は未定義であり、すでに進行中のためA
の初期化は起こりません。言い換えれば、A
が最初にアクセスまたは初期化された場合、順番はA1からA3となり、そうでない場合はB1からB4となる:
MsgBox A.SharedArray.Length MsgBox B.SharedValue class A { static SharedArray := B.SharedArray ; A1 ; B3 static SharedValue := 42 ; B4 } class B { static SharedArray := StrSplit("XYZ") ; A2 ; B1 static SharedValue := A.SharedValue ; A3 (Error) ; B2 }
class ClassName { __Get(Name, Params) __Set(Name, Params, Value) __Call(Name, Params) }
プロパティまたはメソッドの名前です。
パラメータの配列。これは、()
または[]
の間のパラメータのみを含むので、空でも差し支えありません。メタファンクションは、x.y
が未定義のx.y[z]
のようなケースを処理することが期待されます。
割り当てられている値。
メタファンクションは、未定義のプロパティやメソッドが呼び出されたときに何が起こるかを定義します。例えば、obj.unk
に値が割り当てられていない場合、__Getメタファンクションを呼び出します。同様に、obj.unk := value
は __Setを起動し、obj.unk()
は__Callを起動します。
プロパティとメソッドは、オブジェクト自身またはそのベース・オブジェクトのいずれかに定義することができます。一般的に、すべてのプロパティに対してメタファンクションを呼び出すには、プロパティを定義しないようにしなければなりません。Baseのような組み込みプロパティは、プロパティ定義またはDefinePropでオーバーライドできます。
メタファンクションが定義されているときは、そのメタファンクションは必要なデフォルトアクションを全て実行しなければなりません。例えば、次のようなことが予想される:
呼び出し可能なオブジェクトは、関連するプロパティに代入することで、メタファンクションとして使用することができる。
メタファンクションは以下の場合には呼び出されない:
x[y]
:プロパティ名なしで角括弧を使用すると、__Itemプロパティのみが呼び出されます。x()
:オブジェクト自体を呼び出すと、Call
メソッドが呼び出されるだけである。これには、SetTimerや Hotkeyなどの組込関数による内部呼び出しも含まれる。__Call
をトリガーしない。Property構文と DefinePropは、評価されるたびに値を計算するプロパティを定義するために使用できますが、各プロパティは事前に定義する必要があります。対照的に、__Getと __Setは、呼び出された瞬間にしかわからないプロパティを実装するために使うことができる。
例えば、プロパティに対するリクエストをネットワーク経由で(あるいは他のチャネル経由で)送信する「プロキシ」オブジェクトを作成することができる。リモートサーバーはプロパティの値を含む応答を送り返し、プロキシはその値を呼び出し元に返す。各プロパティの名前が事前に分かっていたとしても、どのプロパティも同じこと(ネットワーク・リクエストを送信する)を行うので、プロキシ・クラスで各プロパティを個別に定義するのは論理的ではない。メタ・ファンクションはプロパティ名をパラメータとして受け取るので、この問題に対する良い解決策となる。
文字列や数値などのプリミティブ値は、独自のプロパティやメソッドを持つことはできない。しかし、プリミティブ値はオブジェクトと同じようなデリゲーションをサポートする。つまり、プリミティブ値に対するプロパティやメソッドの呼び出しは、定義済みのプロトタイプ・オブジェクトに委譲される。以下のクラスは、プリミティブ値に関するものです:
Type文字列をチェックする方が一般的に速いが、値の型は、それが指定されたベースを持っているかどうかをチェックすることによってテストすることができる。例えば、n.HasBase(Number.Prototype)
またはn is Number
は、nが純粋なIntegerまたはFloatの場合は真ですが、nが数値文字列の場合は真ではありません。対照的に、IsNumber(n)
はnが数値または数値文字列の場合に真となる。
ObjGetBaseと Baseプロパティは、適切な場合、定義済みのプロトタイプ・オブジェクトの1つを返します。
通常、x is Any
はAutoHotkeyの型階層内のどの値に対してもtrueになりますが、COMオブジェクトに対してはfalseになることに注意してください。
プロパティとメソッドは、その型のプロトタイプ・オブジェクトを修正することによって、与えられた型のすべての値に対して追加することができる。しかし、プリミティブ値はObjectではなく、独自のプロパティやメソッドを持つことができないため、プリミティブプロトタイプオブジェクトはObject.Prototype
から派生しません。つまり、DefinePropや HasOwnPropといったメソッドは、デフォルトではアクセスできない。間接的に呼ばれることもある。事例:
DefProp := {}.DefineProp DefProp( "".base, "Length", { get: StrLen } ) MsgBox A_AhkPath.length " == " StrLen(A_AhkPath)
プリミティブ値はそのプロトタイプから値プロパティを継承できますが、スクリプトがプリミティブ値に値プロパティを設定しようとすると例外がスローされます。事例:
"".base.test := 1 ; Don't try this at home. MsgBox "".test ; 1 "".test := 2 ; エラー:プロパティは読み取り専用です。
Setやプロパティ・セッターを使うこともできるが、プリミティブ値は不変であると考えるべきであるので、これらは有用ではない。
AutoHotkeyは、基本的な参照カウント機構を使用して、オブジェクトがスクリプトから参照されなくなったときに、そのオブジェクトが使用していたリソースを自動的に解放します。Understanding this mechanism can be essential for properly managing the lifetime of an object, allowing it to be deleted when it is no longer needed, and not before then.
An object's reference count is incremented whenever a reference is stored. When a reference is released, the count is used to determine whether that reference is the last one. If it is, the object is deleted; otherwise, the count is decremented. The following example shows how references are counted in some simple cases:
a := {Name: "Bob"} ; Bob's ref count is initially 1 b := [a] ; Bob's ref count is incremented to 2 a := "" ; Bob's ref count is decremented to 1 c := b.Pop() ; Bob is transferred, ref count still 1 c := "" ; Bob is deleted...
式内の関数、メソッド、または演算子によって返された一時参照は、その式の評価が完了または中止された後に解放されます。次の例では、新しいGMemオブジェクトはMsgBoxが返った後にのみ解放される:
MsgBox DllCall("GlobalSize", "ptr", GMem(0, 20).ptr, "ptr") ; 20
注:この例では、Ptrarg型がPtr
プロパティを持つオブジェクトを許可しているため、.ptr
を省略することができました。しかし、上に示したパターンは、他のプロパティ名でも機能する。
オブジェクトへの最後の参照が解放されるときにコードを実行するには、__Deleteメタ関数を実装します。
Relying solely on reference counting sometimes creates catch-22 situations: an object is designed to free its resources when deleted, but would only be deleted if its resources are first freed. Specifically, this occurs when those resources are other objects or functions which retain a reference to the object, often indirectly.
A circular reference or reference cycle is when an object directly or indirectly refers to itself. If each reference which is part of the cycle is included in the count, the object cannot be deleted until the cycle is manually broken. For example, the following creates a reference cycle:
parent := {} ; parent: 1 (reference count) child := {parent: parent} ; parent: 2, child: 1 parent.child := child ; parent: 2, child: 2
If the variables parent
and child
are reassigned, the reference count for each object is decremented to 1. Both objects would be inaccessible to the script, but would not be deleted because the last references are not released.
A cycle is often less obvious than this, and can involve several objects. For example, ShowRefCycleGui demonstrates a cycle involving a Gui, MenuBar, Menu and closures. The use of a separate object to handle GUI events is also prone to cycles, if the handler object has a reference to the GUI.
Non-cyclic references to an object can also cause issues. For instance, objects with a dependency on built-in functions like SetTimer or OnMessage generally cause the program to hold an indirect reference to the object. This would prevent the object from being deleted, which means that it cannot use __New and __Delete to manage the timer or message monitor.
Below are several strategies for solving issues like those described above.
Avoid cycles: If reference cycles are a problem, avoid creating them. For example, either parent.child
or child.parent
would not be set. This is often not practical, as related objects may need a way to refer to each other.
When defining event handlers for OnEvent (Gui), avoid capturing the source Gui in a closure or bound function and instead utilize the Gui or Gui.Control parameter. Likewise for Add (Menu) and the callback's Menu parameter, but of course, a menu item which needs to refer to a Gui cannot use this approach.
In some cases, the other object can be retrieved by an indirect method which doesn't rely on a counted reference. For example, retain a HWND and use GuiFromHwnd(hwnd)
to retrieve a Gui object. Retaining a reference is not necessary to prevent deletion while the window is visible, as the Gui itself handles this.
Break cycles: If the script can avoid relying on reference counting and instead manage the lifetime of the object directly, it needs only break the cycle when the objects are to be deleted:
child.parent := unset ; parent: 1, child: 2 child := unset ; parent: 1, child: 1 parent := unset ; both deleted
Dispose: __Delete is called precisely when the last reference is released, so one might come to think of a simple assignment like myGui := ""
as a cleanup step which triggers deletion of the object. Sometimes this is done explicitly when the object is no longer needed, but it is neither reliable nor truly showing the intent of the code. An alternative pattern is to define a Dispose or Destroy method which frees the object's resources, and design it to do nothing if called a second time. It can then also be called from __Delete, as a safeguard.
An object following this pattern would still need to break any reference cycles when it is disposed, otherwise some memory would not be reclaimed, and __Delete would not be called for other objects referenced by the object.
Cycles caused by a Gui object's event handlers, MenuBar or event sink object are automatically "broken" when Destroy is called, as it releases those objects. (This is demonstrated in the ShowRefCycleGui example.) However, this would not break cycles caused by new properties which the script has added, as Destroy does not delete them.
Similar to the Dispose pattern, InputHook has a Stop method which must be called explicitly, so it does not rely on __Delete to signal when its operation should end. While operating, the program effectively holds a reference to the object which prevents it from being deleted, but this becomes a strength rather than a flaw: event callbacks can still be called and will receive the InputHook as a parameter. When the operation ends, the internal reference is released and the InputHook is deleted if the script has no reference to it.
Pointers: Storing any number of pointer values does not affect the reference count of the object, since a pointer is just an integer. A pointer retrieved with ObjPtr can be used to produce a reference by passing it to ObjFromPtrAddRef. The AddRef version of the function must be used because the reference count will be decremented when the temporary reference is automatically released.
For example, suppose that an object needs to update some properties each second. A timer holds a reference to the callback function, which has the object bound as a parameter. Normally this would prevent the object from being deleted before the timer is deleted. Storing a pointer instead of a reference allows the object to be deleted regardless of the timer, so it can be managed automatically by __New and __Delete.
a := SomeClass() Sleep 5500 ; Let the timer run 5 times. a := "" Sleep 3500 ; Prevent exit temporarily to show that the timer has stopped. class SomeClass { __New() { ; The closure must be stored so that the timer can be deleted later. ; Synthesize a counted reference each time the method needs to be called. this.Timer := (p => ObjFromPtrAddRef(p).Update()).Bind(ObjPtr(this)) SetTimer this.Timer, 1000 } __Delete() { SetTimer this.Timer, 0 ; If this object is truly deleted, all properties will be ; deleted and the following __Delete method will be called. ; This is just for confirmation and wouldn't normally be used. this.Test := {__Delete: test => ToolTip("object deleted")} } ; This is just to demonstrate that the timer is running. ; Hypothetically, this class has some other purpose. count := 0 Update() => ToolTip(++this.count) }
A drawback of this approach is that the pointer is not directly usable as an object, and is not recognized as such by Type or the debugger. The script must be absolutely certain not to use the pointer after the object is deleted, as doing so is invalid and the result would be indeterminate.
If the pointer-reference is needed in multiple places, encapsulating it might make sense. For instance, b := ObjFromPtrAddRef.Bind(ObjPtr(this))
would produce a BoundFunc which can be called (b()
) to retrieve the reference, while ((this, p) => ObjFromPtrAddRef(p)).Bind(ObjPtr(this))
can be used as a property getter (the property would return a reference).
Uncounted references: If the object's reference count accounts for a reference, we call it a counted reference, otherwise we call it an uncounted reference. The idea of the latter is to allow the script to store a reference which does not prevent the object from being deleted.
Note: This is about how the object's reference count relates to a given reference as per the script's logic, and doesn't affect the nature of the reference itself. The program will still attempt to release the reference automatically at whatever time it would normally, so the terms weak reference and strong reference are unsuitable.
A counted reference can be turned into an uncounted reference by simply decrementing the object's reference count. This must be reversed before the reference is released, which must occur before the object is deleted. Since the point of an uncounted reference is to allow the object to be deleted without first manually unsetting the reference, generally the count must be corrected within that object's own __Delete method.
For example, __New and __Delete from the previous example can be replaced with the following.
__New() { ; The BoundFunc must be stored so that the timer can be deleted later. SetTimer this.Timer := this.Update.Bind(this), 1000 ; Decrement ref count to compensate for the AddRef done by Bind. ObjRelease(ObjPtr(this)) } __Delete() { ; Increment ref count so that the ref within the BoundFunc ; can be safely released. ObjPtrAddRef(this) ; Delete the timer to release its reference to the BoundFunc. SetTimer this.Timer, 0 ; Release the BoundFunc. This may not happen automatically ; due to the reference cycle which exists now that the ref ; in the BoundFunc is counted again. this.Timer := unset ; If this object is truly deleted, all properties will be ; deleted and the following __Delete method will be called. ; This is just for confirmation and wouldn't normally be used. this.Test := {__Delete: test => ToolTip("object deleted")} }
This can generally be applied regardless of where the uncounted reference is stored and what it is used for. The key points are:
The reference count must be incremented and decremented as many times as there are references which are intended to be uncounted. This may not be practical if the script cannot accurately predict how many references will be stored by some function.
As part of creating an object, some memory is allocated to hold the basic structure of the object. This structure is essentially the object itself, so we call its address a pointer to the object. An address is an integer value which corresponds to a location within the virtual memory of the current process, and is valid only until the object is deleted.
まれに、DllCallを介して外部コードにオブジェクトを渡したり、後で取り出すためにバイナリ・データ構造に格納したりする必要がある場合がある。オブジェクトのアドレスは、address := ObjPtr(myObject)
で取得することができる。しかし、これは事実上オブジェクトへの参照を2つ作ることになり、プログラムはmyObjectの中の1つしか知らない。オブジェクトへの最後の既知の参照が解放された場合、オブジェクトは削除される。したがって、スクリプトはオブジェクトに参照を得たことを通知しなければならない。これは以下のようにできる(以下の2行は同等):
ObjAddRef(address := ObjPtr(myObject)) address := ObjPtrAddRef(myObject)
スクリプトはまた、その参照を終了するときにオブジェクトに通知しなければならない:
ObjRelease(アドレス)
一般に、オブジェクトのアドレスの新しいコピーはそれぞれ、そのオブジェクトへの別の参照として扱われるべきです。したがって、スクリプトは、コピーを得たときにObjAddRefを呼び出し、コピーを失う直前にObjReleaseを呼び出す必要があります。例えば、x := address
のようにアドレスがコピーされるときは、必ずObjAddRefが呼ばれなければならない。同様に、スクリプトがxの処理を終えたら(あるいはxの値を上書きしようとしたら)、ObjReleaseを呼び出すべきである。
アドレスを適切な参照に変換するには、ObjFromPtr関数を使用します:
myObject := ObjFromPtr(address)
ObjFromPtrは、アドレスがカウントされた参照であると仮定し、その所有権を主張する。つまり、myObject := ""
とすると、もともとaddressで表されていた参照が解放される。それ以降のアドレスは無効とみなされる。その代わりに新しいリファレンスを作成するには、以下のいずれかを使用する:
ObjAddRef(address), myObject := ObjFromPtr(address) myObject := ObjFromPtrAddRef(address)