객체

오토핫키에서 객체(object)는 추상 데이터 유형으로서 세 가지 기본 기능을 제공합니다:

관련 주제:

IsObject()를 사용하면 값이 객체인지 판별할 수 있습니다:

Result := IsObject(expression)

See Object Types in the documentation side bar for a list of standard object types. There are three fundamental types:

Support for objects requires [AHK_L 31+], but some features may require a later version.

목차

기본 사용법

단순한 배열 [v1.1.21+]

배열을 만듭니다:

Array := [Item1, Item2, ..., ItemN]
Array := Array(Item1, Item2, ..., ItemN)

항목을 열람합니다:

Value := Array[Index]

항목을 할당합니다:

Array[Index] := Value

Insert one or more items at a given index using the InsertAt method:

Array.InsertAt(Index, Value, Value2, ...)

Append one or more items using the Push method:

Array.Push(Value, Value2, ...)

Remove an item using the RemoveAt method:

RemovedValue := Array.RemoveAt(Index)

Remove the last item using the Pop method:

RemovedValue := Array.Pop()

배열이 비어 있지 않으면, MinIndexMaxIndex/Length는 배열에서 현재 사용중인 가장 낮은 인덱스와 가장 높은 인덱스를 돌려줍니다. 가장 낮은 인덱스는 거의 대부분 1이므로, MaxIndex는 보통 항목의 개수를 돌려줍니다. 그렇지만, 정수 키가 없다면, MaxIndex는 빈 문자열을 돌려줍니다. 반면에 Length는 0을 돌려줍니다. 배열의 내용은 인덱스 또는 For-loop로 회돌이할 수 있습니다. 예를 들어:

array := ["one", "two", "three"]

; 1부터 배열의 끝까지 반복합니다:
Loop % array.Length()
    MsgBox % array[A_Index]

; 배열의 내용을 열거합니다:
For index, value in array
    MsgBox % "Item " index " is '" value "'"

연관 배열 [v1.1.21+]

연관 배열은 유일한 키 집단과 유일한 값 집단을 포함하는 객체입니다. 각 키는 각 값에 연관되어 있습니다. 키는 문자열이나 정수또는 객체가 될 수 있는 반면, 값은 어떤 유형도 될 수 있습니다. 연관 배열을 다음과 같이 만들 수 있습니다:

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

{key:value} 표기법을 사용하면, 오직 단어 문자로만 구성된 키에는 인용부호를 생략해도 됩니다. 어떤 표현식도 키로 사용할 수는 있지만, 변수를 키로 사용하려면, 반드시 괄호로 둘러싸야 합니다. 예를 들어, {(KeyVar): Value}{GetKey(): Value}는 모두 유효합니다.

항목을 열람합니다:

Value := Array[Key]

항목을 할당합니다:

Array[Key] := Value

Remove an item using the Delete method:

RemovedValue := Array.Delete(Key)

항목을 열거합니다:

array := {ten: 10, twenty: 20, thirty: 30}
For key, value in array
    MsgBox %key% = %value%

연관 배열은 중간 중간 비워서 만들 수 있습니다 - 즉, {1:"a",1000:"b"}는 오직 두 개의 키-값 쌍만 들어 있습니다. 1000개가 아닙니다.

AutoHotkey v1.x에서 단순 배열과 연관 배열은 같은 것입니다. 그렇지만 []를 단순 배열로 간주하는 것이 그의 목적을 명확하게 보여주는 데 도움이 되고, 미래 버전의 오토핫키에 스크립트가 작동할 가능성을 높여줍니다. 앞으로는 단순 배열과 연관 배열 사이에 차이가 있을 수 있습니다.

객체 [AHK_L 31+]

For all types of objects, the notation Object.LiteralKey can be used to access a property, array element or method, where LiteralKey is an identifier or integer and Object is any expression. Identifiers are unquoted strings which may consist of alphanumeric characters, underscore and, in [v1.1.09+], non-ASCII characters. For example, match.Pos is equivalent to match["Pos"] while arr.1 is equivalent to arr[1]. There must be no space after the dot.

예제:

특성을 열람합니다:

Value := Object.Property

특성을 설정합니다:

Object.Property := Value

메쏘드를 호출합니다:

ReturnValue := Object.Method(Parameters)

계산한 메쏘드 이름으로 메쏘드를 호출합니다:

ReturnValue := Object[MethodName](Parameters)

COM 객체의 특성과 사용자-정의 객체는 매개변수를 받을 수 있습니다:

Value := Object.Property[Parameters]
Object.Property[Parameters] := Value

관련 항목: Object, File Object, Func Object, COM object

알려진 한계:

객체 풀어주기

스크립트는 명시적으로 객체를 풀어주지 않습니다. 객체를 가리키는 마지막 참조가 사라지면, 그 객체는 자동으로 해제됩니다. 변수에 저장된 참조는 그 변수가 다른 값에 할당되면 자동으로 해제됩니다. 예를 들어:

obj := {}  ; 객체를 생성합니다.
obj := ""  ; 마지막 참조를 해제합니다. 그러므로 객체가 해방됩니다.

비슷하게, 또다른 객체의 필드에 저장된 참조점은 그 필드가 또다른 값에 할당되거나 그 객체로부터 제거될 때 자동으로 풀어집니다. 이것은 배열에도 적용됩니다. 배열은 실제로 객체입니다.

arr := [{}]  ; 객체를 담고 있는 배열을 생성합니다.
arr[1] := {}  ; 두 번째 객체를 생성합니다. 묵시적으로 첫 객체를 풀어주고 있습니다.
arr.RemoveAt(1)  ; 두 번째 객체를 제거하고 풀어줍니다.

한 객체를 가리키는 모든 참조가 해제되어야 비로서 그 객체가 해방될 수 있습니다. 그래서 순환 참조가 있는 객체는 자동으로 풀어지지 않습니다. 예를 들면, x.childy를 가리키고 y.parentx를 가리킨다면, xy를 소거하는 것만으로는 충분하지 않습니다. 왜냐하면 부모 객체에 여전히 자손을 가리키는 참조가 들어있고 그 반대도 마찬가지이기 때문입니다. 이 문제를 해결하려면, 순환 참조를 제거해야 합니다.

x := {}, y := {}             ; 객체를 두 개 생성합니다.
x.child := y, y.parent := x  ; 순환 참조를 생성합니다.

y.parent := ""               ; 순환 참조를 먼저 제거해야 객체가 해방될 수 있습니다.
x := "", y := ""             ; 위의 줄이 없다면, 이 줄로는 객체를 풀어줄 수 없습니다.

더 자세한 내용과 고급 사용법은 참조 횟수 세기를 보십시오.

논평

구문

모든 유형의 객체는 배열 구문(각괄호)과 객체 구문 (점)을 지원합니다.

게다가, 객체 참조는 표현식에 사용할 수 있습니다:

객체가 예상치 못한 문맥에서 사용중이면, 그 객체는 빈 문자열로 취급됩니다. 예를 들어, MsgBox %object% 는 빈 MsgBox를 보여주고 object + 1는 빈 문자열을 산출합니다. 이 행위는 바뀔 수 있으므로 여기에 의존하면 안됩니다.

메쏘드-호출 다음에 곧바로 할당 연산자가 오면, 매개변수로 특성을 할당하는 것과 동등합니다. 예를 들어, 다음은 서로 동등합니다:

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

x.y += 1--arr[1]같은 복합 서술문을 지원합니다.

[v1.1.20+]: 특성을 설정하거나 획득할 때 매개변수를 생략할 수 있습니다. 예를 들어, x[,2]. 스크립트는 이를 이용하여 특성메타-함수의 매개변수에 기본 값들을 정의할 수 있습니다. 메쏘드 이름도 x[](a)처럼 완전히 생략 가능합니다. Scripts can utilize this by defining a default value for the __Call meta-function's first parameter, since it is not otherwise supplied with a value. 이것은 x.(a)와 다르며, x[""](a)과 동등한 것에 주목하십시오. COM 객체를 호출할 때 특성이나 메쏘드 이름이 생략되면, 그의 "기본 멤버"가 호출됩니다.

Keys

[], {} 또는 new 연산자로 생성된 객체에 어느 값을 사용할 수 있는지는 약간 제한이 있습니다 :

확장 사용법

함수 참조 [v1.1.00+]

변수 func에 함수 이름이 들어 있다면, 그 함수는 두 가지 방법으로 호출할 수 있습니다: %func%() 또는 func.(). 그렇지만, 이렇게 하려면 매번 함수 이름을 결정해야 하는데, 함수가 여러번 호출된다면 효율적이지 못합니다. 수행성능을 개선하기 위해, 스크립트는 함수에 대한 참조를 열람해 그것을 저장해 나중에 사용할 수 있습니다:

Func := Func("MyFunc")

함수는 다음과 같은 구문을 사용하여 참조로 호출할 수 있습니다:

RetVal := %Func%(Params)     ; [v1.1.07+] 필요
RetVal := Func.Call(Params)  ; [v1.1.19+] 필요
RetVal := Func.(Params)      ; 권장하지 않습니다

함수 참조의 추가 특성들에 관한 정보는 Func Object를 참조하십시오.

배열의 배열

오토핫키는 "다-차원" 배열을 지원합니다. 다른 배열 안에 배열을 투명하게 저장하면 됩니다. 예를 들어, 테이블은 행의 배열로 표현할 수 있습니다. 반면에 각 행 자체는 열의 배열입니다. 그 경우, xy열의 내용은 아래의 방법중 하나를 사용하여 설정할 수 있습니다:

table[x][y] := content  ; A
table[x, y] := content  ; B

table[x]가 존재하지 않으면, AB는 두 가지 점에서 다릅니다:

table[a, b, c, d] := value와 같은 다-차원 할당은 다음과 같이 처리됩니다:

다음 행위는 오직 스크립트로-만든 객체에만 적용됩니다. 좀 더 전문화된 유형의 객체, COM 객체나 COM 배열은 적용되지 않습니다.

함수 배열

함수 배열은 단순히 함수 이름이나 참조를 담고 있는 배열입니다. 예를 들어:

array := [Func("FirstFunc"), Func("SecondFunc")]

; 각 함수를 호출한다, "foo"를 매개변수로 건넨다:
Loop 2
    array[A_Index].Call("foo")

; 각 함수를 호출한다. 묵시적으로 배열 자체를 매개변수로 건넨다:
Loop 2
    array[A_Index]()

FirstFunc(param) {
    MsgBox % A_ThisFunc ": " (IsObject(param) ? "object" : param)
}
SecondFunc(param) {
    MsgBox % A_ThisFunc ": " (IsObject(param) ? "object" : param)
}

하위-호환을 위해, array[A_Index]에 함수 참조가 아니라 함수 이름이 들어 있다면, 두 번째 형태는 배열(array)을 매개변수로 건네지 않습니다. 그렇지만, array[A_Index]array.base[A_Index]으로부터 상속되었다면, 배열(array)이 매개변수로 건네집니다.

맞춤 객체

스크립트로 만든 객체는 미리 정의된 구조를 가질 필요가 없습니다. Instead, each object can inherit properties and methods from its base object (otherwise known as a "prototype" or "class"). 특성과 메쏘드는 언제든지 객체에 추가할 수도 (제거할 수도) 있습니다. 이런 변경은 이로부터 파생된 모든 객체에 영향을 미칩니다. 보다 복잡하고 전화된 상황을 위해, 베이스 객체는 자신을 상속받은 객체들의 표준 행위를 오버라이드 할 수 있습니다. 메타-함수를 정의해 오버라이드 합니다.

Base 객체는 그냥 평범한 객체이며, 전형적으로 두 가지 방식으로 생성됩니다:

class baseObject {
    static foo := "bar"
}
; 또는
baseObject := {foo: "bar"}

객체를 또다른 객체로부터 파생시켜 생성하기 위해, 스크립트는 base 특성에 할당하거나 new 키워드를 사용할 수 있습니다:

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

It is possible to reassign an object's base at any time, effectively replacing all of the properties and methods that the object inherits.

프로토타입

Prototype or base objects are constructed and manipulated the same as any other object. 예를 들어, 특성 하나와 메쏘드 하나를 가진 평범한 객체는 다음과 같이 구성할 수 있습니다:

; 객체를 생성한다.
thing := {}
; Store a value.
thing.foo := "bar"
; 함수 참조를 저장함으로써 메쏘드를 생성한다.
thing.test := Func("thing_test")
; 메쏘드를 호출한다.
thing.test()

thing_test(this) {
    MsgBox % this.foo
}

thing.test()가 호출되면, thing는 자동으로 매개변수 리스트의 첫번째로 삽입됩니다. 그렇지만, 하위 호환을 위해, 이런 일은 함수가 (참조가 아니라) 이름으로 (베이스 객체로부터 상속받는 것이 아니라) 직접적으로 객체에 저장될 경우에만 일어납니다. 관례적으로 함수는 객체의 유형과 메쏘드의 이름을 결합해 짓습니다.

객체는 또다른 객체가 그로부터 파생해 나가면 프로토타입(prototype) 또는 베이스(base)가 됩니다:

other := {}
other.base := thing
other.test()

이 경우, otherfoo를 상속받고 testthing으로부터 상속을 받습니다. 이런 상속 관계는 역동적입니다. 그래서 thing.foo가 변경되면, 그 변경이 other.foo에 반영됩니다. 스크립트가 other.foo에 할당하면, 그 값은 other에 저장됩니다. thing.foo에 변경을 더 가하더라도 other.foo에는 영향을 미치지 않습니다. other.test()가 호출되면, 그의 this 매개변수에는 thing이 아니라 other를 가리키는 참조가 들어갑니다.

클래스 [v1.1.00+]

어원으로 말하자면 "클래스"란 공통의 특성이나 속성을 가지고 있는 것들의 한 범주 또는 집합입니다. base 객체나 prototype 객체는 객체 집합에 대하여 특성과 행위를 정의하기 때문에, 클래스(class) 객체라고 부를 수도 있습니다. 편의를 위해, 베이스 객체는 아래에 보여주는 바와 같이 "class" 키워드를 사용하여 정의할 수 있습니다:

class ClassName extends BaseClassName
{
    InstanceVar := Expression
    static ClassVar := Expression

    class NestedClass
    {
        ...
    }

    Method()
    {
        ...
    }

    Property[]  ; 각괄호는 선택적이다
    {
        get {
            return ...
        }
        set {
            return ... := value
        }
    }
}

스크립트가 적재될 때, 이 코드는 객체를 하나 구성하고 그것을 전역 변수 (또는 [in v1.1.05+]이라면 수퍼-전역 변수) ClassName에 저장합니다. To reference this class inside a force-local function (or an assume-local or assume-static function prior to [v1.1.05]), a declaration such as global ClassName is required. extends BaseClassName가 있으면, BaseClassName은 반드시 또다른 클래스의 완전한 이름이어야 합니다 (그러나 [v1.1.11]에서는 정의된 순서는 문제가 되지 않습니다). 각 클래스의 완전한 이름이 object.__Class에 저장됩니다.

Because the class is referenced via a variable, the class name cannot be used to both reference the class and create a separate variable (such as to hold an instance of the class) in the same context. For example, box := new Box would replace the class object in Box with an instance of itself. [v1.1.27+]: #Warn ClassOverwrite enables a warning to be shown at load time for each attempt to overwrite a class.

이 문서 안에서 클래스("class")라는 단어는 그 자체로 보통 class 키워드로 구성된 클래스 객체를 의미합니다.

클래스 정의는 변수 선언, 메쏘드 정의, 내포 클래스 정의를 포함할 수 있습니다.

실체 변수 [v1.1.01+]

실체 변수는 클래스의 각 실체가 자신만의 클래스 사본을 가진 것입니다. 보통의 할당처럼 선언되지만, this. 접두사가 생략되어 있습니다 (클래스 몸체 안에서 직접적으로 사용될 경우에만 생략합니다):

InstanceVar := Expression

이런 선언은 클랫의 새 실체가 new 키워드로 생성될 때마다 매번 평가됩니다. 메쏘드 이름 __Init은 이런 목적을 위해 예약되어 있으며, 다른 스크립트에서 사용하면 안됩니다. __New() 메쏘드는 베이스 클래스에 정의된 선언을 포함하여 그 모든 선언이 평가된 후에 호출됩니다. 표현식은 다른 실체 변수와 메소드에 this를 통하여 접근할 수 있지만, 모든 다른 변수 참조는 전역적이라고 간주됩니다.

실체 변수에 접근하려면 (메쏘드 안에서도 마찬가지로), 언제나 목표 객체를 지정합니다; 예를 들어, this.InstanceVar.

[v1.1.08+]: 이 클래스에 x가 미리 선언되어 있다면 x.y := z와 같은 선언도 지원됩니다. 예를 들어, x := {}, x.y := 42x를 선언하고 또 this.x.y를 초기화합니다.

정적/클래스 변수 [v1.1.00.01+]

정적/클래스 변수는 클래스 자체에 속하지만, (하위-클래스를 포함하여) 파생 객체가 상속받을 수 있습니다. 실체 변수처럼 선언되지만, static 키워드를 사용합니다:

static ClassVar := Expression

정적 선언은 오직 자동-실행 섹션 전에 한 번만 평가됩니다. 그리고 순서대로 스크립트에 나타납니다. 각 선언은 값을 object 클래스에 저장합니다. 표현식 안의 모든 변수 참조는 전역적이라고 간주됩니다.

To assign to a class variable, always specify the class object; for example, ClassName.ClassVar := Value. If an object x is derived from ClassName and x itself does not contain the key "ClassVar", x.ClassVar may also be used to dynamically retrieve the value of ClassName.ClassVar. However, x.ClassVar := y would store the value in x, not in ClassName.

[v1.1.08+]: 이 클래스에 x가 미리 정의되어 있다면 static x.y := z와 같은 표현도 지원합니다. 예를 들어, static x := {}, x.y := 42x를 선언하고 또 ClassName.x.y를 초기화합니다.

내포 클래스

내포 클래스 정의는 클래스 객체를 또다른 전역 변수가 아니라 또다른 클래스 객체 안에 저장되도록 허용합니다. 위의 예제에서, class NestedClass는 객체를 구성하고 그것을 ClassName.NestedClass에 저장합니다. 하위-클래스는 NestedClass를 상속받거나 자신의 내포 클래스로 그것을 오버라이드할 수 있습니다 (이 경우 new this.NestedClass를 사용하여 적절한 클래스를 실체화할 수 있습니다).

class NestedClass
{
    ...
}

메쏘드

메쏘드 정의는 함수 정의와 모습이 똑 같습니다. 각 메쏘드는 숨은 매개변수로 this가 있는데, 이것은 전형적으로 클래스로부터 상속받은 객체를 가리키는 참조점을 담고 있습니다. 그렇지만, 메쏘드가 어떻게 호출되는 가에 따라 클래스 자체나 상속받은 클래스를 가리키는 참조점을 담을 수도 있습니다. 메쏘드는 이 클래스 객체에 참조로 저장됩니다.

Method()
{
    ...
}

메쏘드 안에서, 의사-키워드 base를 사용하면, 파생 클래스 안에 오버라이드된 수퍼-클래스의 메쏘드나 특성에 접근할 수 있습니다. 예를 들어, 위에 정의된 클래스에서 base.Method()BaseClassName에 정의된 Method 버전을 호출합니다. 메타-함수는 호출되지 않습니다; 그렇지 않으면, base.Method()는 마치 BaseClassName.Method.Call(this)처럼 행위합니다. 다시 말해,

base는 다음에 점. 또는 각괄호 []가 따라오면 특별한 의미를 가집니다. 그래서 obj := base, obj.Method()와 같은 코드는 작동하지 않습니다. 스크립트는 base의 특별한 행위를 불능으로 만들 수 있습니다. 거기에다 비어있지-않은 값을 할당하면 됩니다; 그렇지만, 이것은 권장하지 않습니다. 왜냐하면 변수 base는 꼭 비어 있어야 합니다. 스크립트가 #NoEnv를 빼먹으면 수행성능이 저하될 수 있기 때문입니다.

Properties [v1.1.16+]

특성 정의는 스크립트가 특정한 키를 설정하거나 열람할 때마다 메쏘드를 실행하도록 허용합니다.

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

Property는 그냥 호출하는 데 사용되는 특성의 이름일 뿐입니다. 예를 들면, obj.Propertyget을 호출하는 반면에 obj.Property := valueset을 호출합니다. get이나 set 안에서, this는 요청 중인 객체를 가리킵니다. set 안에서, value에는 할당된 값이 담깁니다.

매개변수는 특성 이름의 오른쪽에 각괄호로 둘러싸 건넬 수 있습니다. 특성을 정의할 때와 호출할 때 모두 마찬가지입니다. 각괄호를 사용하는 외에도, 특성의 매개변수는 메쏘드의 매개변수와 같은 방식으로 정의됩니다 - 선택적, ByRef 그리고 가변 매개변수를 지원합니다.

get이나 set의 반환값은 해당 특성을 요청한 하위-표현식의 결과가 됩니다. 예를 들어, val := obj.Property := 42set의 반환 값을 val에 저장합니다.

각 클래스는 특성의 절반만 또는 둘 다 온전하게 정의할 수 있습니다 (특성=속성). 클래스가 특성을 오버라이드 하면 base.Property를 사용하여 자신의 기본 클래스에 정의된 특성에 접근할 수 있습니다. get이나 set이 정의되어 있지 않으면, 기본 클래스가 처리할 수 있습니다. set이 정의되어 있지 않고 메타-함수나 기본 클래스에 의하여 처리되지 않은 경우, 값을 객체에 저장하면 그 특성이 불능이 되는 효과가 있습니다.

내부적으로, getset은 두 개의 다른 메쏘드입니다. 그래서 변수를 공유할 수 없습니다 (this에 저장한 변수는 가능합니다).

메타-함수는 객체의 메쏘드와 특성에 접근을 보다 넓게 제어합니다. 그러나 더 복잡하고 에러를 일으키는 경향이 더 높습니다.

구성과 파괴

파생 객체가 new 키워드로 생성될 때마다 [requires v1.1.00+], 그의 기반 클래스에 정의된 __New 메쏘드가 호출됩니다. 이 메쏘드는 매개변수를 받아, 그 객체를 초기화하고 new 연산자의 결과를 값을 돌려줌으로써 오버라이드할 수 있습니다. 객체가 파괴될 때, __Delete가 호출됩니다. 예를 들어:

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

class GMem
{
    __New(aFlags, aSize)
    {
        this.ptr := DllCall("GlobalAlloc", "UInt", aFlags, "Ptr", aSize, "Ptr")
        if !this.ptr
            return ""
        MsgBox % "New GMem of " aSize " bytes at address " this.ptr "."
        return this  ; 'new' 연산자를 사용하면 이 줄은 생략할 수 있다.
    }

    __Delete()
    {
        MsgBox % "Delete GMem at address " this.ptr "."
        DllCall("GlobalFree", "Ptr", this.ptr)
    }
}

__Delete는 "__Class"를 키로 가지는 객체에 호출되지 않습니다. 클래스 객체가 이 키를 기본값으로 가집니다.

If the class has a super-class which defines these methods, base.__New() (with parameters as appropriate) and base.__Delete() should typically be called. Otherwise, only the most derived definition of the method is called, excluding any definition within the target object itself.

[v1.1.28+]: If an exception or runtime error is thrown while __Delete is executing and is not handled within __Delete, it acts as though __Delete was called from a new thread. That is, an error dialog is displayed and __Delete returns, but the thread does not exit (unless it was already exiting). Prior to v1.1.28, unhandled exceptions caused inconsistent behavior.

If the script is directly terminated by any means, including the tray menu, ExitApp, or Exit (when the script is not persistent), any functions which have yet to return do not get the chance to do so. Therefore, any objects referenced by local variables of those functions are not released, so __Delete is not called.

When the script exits, objects contained by global and static variables are released automatically in an arbitrary, implementation-defined order. When __Delete is called during this process, some global or static variables may have already been released, but any references contained by the object itself are still valid. It is therefore best for __Delete to be entirely self-contained, and not rely on any global or static variables.

메타-함수

메쏘드 구문:
class ClassName {
    __Get([Key, Key2, ...])
    __Set([Key, Key2, ...], Value)
    __Call(Name [, Params...])
}

함수 구문:
MyGet(this [, Key, Key2, ...])
MySet(this [, Key, Key2, ...], Value)
MyCall(this, Name [, Params...])

ClassName := { __Get: Func("MyGet"), __Set: Func("MySet"), __Call: Func("MyCall") }

메타-함수는 목표 객체 안에 키를 요청했지만 안에서 발견할 수 없을 때 일어날 행위를 정의합니다. 예를 들어, obj.key에 값이 할당되지 않았다면, __Get 메타-함수를 요청합니다. 비슷하게, obj.key := value__Set를 요청하고 obj.key()__Call를 호출합니다. 이 메타-함수들 (또는 메쏘드들)은 obj.base, obj.base.base 등등에 정의할 필요가 있습니다.

Meta-functions are generally defined like methods, but do not follow the same rules (except when called explicitly by the script). They must be defined in a base object; any definition in the target object itself is ignored. Each definition of __Get, __Set and __Call applicable to the target object is called automatically according to the rules below, and should not call base.__Get(key) or similar. __New and __Delete must be defined in a base object, but otherwise behave like methods.

Note: AutoHotkey v2 replaces meta-functions with more conventional methods.

스크립트가 목표 객체 안에 존재하지 않는 키를 열람하거나 설정하거나 또는 호출하면, 베이스 객체가 다음과 같이 요청됩니다:

일치하는 키를 메타-함수가 객체에 저장하지만 반환(return)하지 않으면, 그 행위는 마치 키가 처음부터 그 객체에 존재한 것과 같습니다. __Set을 사용하는 예제는 배열의 배열 서브-클래싱하기를 참조하십시오.

연산이 여전히 처리되지 않았다면, 이것이 내장 함수인지 아니면 내장 특성인지 점검하십시오:

여전히 연산이 처리되지 않았다면,

알려진 한계:

동적 특성

Property syntax can be used to define properties which compute a value each time they are evaluated, but each property must be known in advance and defined individually in the script. By contrast, __Get and __Set can be used to implement properties which aren't known by the script.

For example, a "proxy" object could be created which sends requests for properties over the network (or through some other channel). A remote server would send back a response containing the value of the property, and the proxy would return the value to its caller. Even if the name of each property was known in advance, it would not be logical to define each property individually in the proxy class since every property does the same thing (send a network request). Meta-functions receive the property name as a parameter, so are a good solution for this problem.

Another use of __Get and __Set is to implement a set of related properties which share code. In the example below they are used to implement a "Color" object with R, G, B and RGB properties, where only the RGB value is actually stored:

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

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

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

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

    __Get(aName)
    {
        ; NOTE: Using this.Shift here would cause an infinite loop!
        shift := Color.Shift[aName]  ; Get the number of bits to shift.
        if (shift != "")  ; Is it a known property?
            return (this.RGB >> shift) & 0xff
        ; NOTE: Using 'return' here would break this.RGB.
    }

    __Set(aName, aValue)
    {
        if ((shift := Color.Shift[aName]) != "")
        {
            aValue &= 255  ; Truncate it to the proper range.

            ; Calculate and store the new RGB value.
            this.RGB := (aValue << shift) | (this.RGB & ~(0xff << shift))

            ; 'Return'을 사용하여 새 키-값 쌍이 만들어지지 않았음을 알려야 합니다.
            ; 또한 'x := clr[name] := val'에서 무엇을 'x'에 저장할지 정의합니다:
            return aValue
        }
        ; NOTE: Using 'return' here would break this.stored_RGB and this.RGB.
    }

    ; Meta-functions can be mixed with properties:
    RGB {
        get {
            ; Return it in hex format:
            return format("0x{:06x}", this.stored_RGB)
        }
        set {
            return this.stored_RGB := value
        }
    }
}

However, in this case Property syntax could have been used instead, where code is shared by simply having each property call a central method. It is better to avoid using meta-functions where possible due to the high risk of misuse (see the notes in red above).

함수로서의 객체

For an outline of how to create objects which can act as functions, see Function Objects.

A function object can also act as a meta-function, such as to define dynamic properties similar to those in the previous section. Although it is recommended to use property syntax instead, the example below shows the potential of meta-functions for implementing new concepts or behaviour, or changing the structure of the script.

; This example requires the FunctionObject class in order to work.
blue := new Color(0x0000ff)
MsgBox % blue.R "," blue.G "," blue.B

class Properties extends FunctionObject
{
    Call(aTarget, aName, aParams*)
    {
        ; 이 특성 객체에 이 절반-특성에 대한 정의가 들어 있다면, 그것을 호출합니다.
        if ObjHasKey(this, aName)
            return this[aName].Call(aTarget, aParams*)
    }
}

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

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

    ;...
}

배열의 배열을 서브-클래싱하기

table[x, y] := content와 같이 다중-매개변수 할당 때문에 묵시적으로 새로운 객체가 생성될 때, 그 새 객체는 보통 베이스가 없고 그러므로 맞춤 메쏘드도 없고 특별한 행위도 없습니다. __Set는 아래에 보여주는 바와 같이 이런 객체들을 초기화하는 데 사용할 수 있습니다.

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

; 값을 할당한다. 묵시적으로 x_Setter를 사용하여 하위-객체를 만든다.
x[1,2,3] := "..."

; 값을 열람하고 예제 메쏘드를 호출한다.
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는 네 개의 필수 매개변수가 있기 때문에, 두 개 이상의 키가 있을 경우에만 호출됩니다. 위의 할당이 일어날 때, 다음과 같은 일이 일어납니다:

기본 베이스 객체

비-객체 값이 객체 구문과 함께 사용될 때, 기본 베이스 객체가 요청됩니다. 이것은 디버깅에 사용하거나 또는 문자열이나, 숫자, 그리고/또는 변수에 대하여 객체-류의 행위를 전역적으로 정의하는데 사용할 수 있습니다. 기본 베이스는 비-객체 값을 가지고 .base를 사용하여 접근이 가능합니다; 예를 들면, "".base. 기본 베이스는 "".base := Object()처럼 설정(set)할 수 없지만, 기본 베이스는 "".base.base := Object()처럼 그 자체로 베이스가 있습니다.

자동 변수 초기화

set 연산의 목표로 빈 변수가 사용될 때, 직접적으로 __Set meta-함수에 건네집니다. 그래서 새 객체를 변수에 삽입할 기회가 있습니다. 간략하게 하기 위해, 다음 예제는 다중 매개변수를 지원하지 않습니다; 물론 가변 함수를 사용하면 가능합니다.

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

의사-특성

객체의 "사탕 구문"을 문자열과 숫자에 적용할 수 있습니다.

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

내장 함수도 역시 사용할 수 있지만, 이 경우 괄호를 빼먹으면 안됩니다. 주의하십시오:

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

디버깅

값을 객체처럼 취급되도록 허용하면 바람직하지 않습니다. 비-객체 값이 요청될 때마다 경고 상자가 나타날 수 있습니다:

"".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 비-객체 값이 부적절하게 요청되었습니다.`n`n특히: %nonobj%
}

구현

참조횟수-세기

오토핫키는 기본적인 참조횟수-세기 매커니즘을 사용하여 한 객체가 사용하는 자원이 더 이상 참조되지 않을 때 자동으로 해제합니다. Script authors should not invoke this mechanism explicitly, except when dealing directly with unmanaged pointers to objects.

Currently in AutoHotkey v1.1, temporary references created within an expression (but not stored anywhere) are released immediately after use. For example, Fn(&{}) passes an invalid address to the function, because the temporary reference returned by {} is released immediately after the address-of operator is evaluated.

객체의 마지막 참조가 해제될 때 코드를 실행하려면, __Delete 메타-함수를 구현합니다.

알려진 한계:

프로그램이 종료할 때 객체가 사용하는 메모리를 운영체제가 요구함에도 불구하고, 그 객체를 가리키는 모든 참조가 풀리지 않는 한, __Delete는 호출되지 않습니다. 이것은 임시 파일과 같이, 운영 체제가 자동으로 요구하지 않는 다른 자원을 풀어버리면 문제가 될 수 있기 때문입니다.

객체를 가리키는 포인터

어떤 경우는 DllCall()을 통하여 객체를 외부 코드에 건넬 필요가 있습니다. 또는 그것을 이진 데이터 구조로 저장하여 나중에 열람할 필요가 있습니다. An object's address can be retrieved via address := &object; however, this effectively makes two references to the object, but the program only knows about the one in object. If the last known reference to the object was released, the object would be deleted. Therefore, the script must inform the object that it has gained a reference. There are two ways to do this:

; Method #1: Explicitly increment the reference count.
address := &object
ObjAddRef(address)

; Method #2: Use Object(), which increments the reference count and returns an address.
address := Object(object)

This function can also be used to convert an address back into a reference:

object := Object(address)

Either way, the script must also inform the object when it is finished with that reference:

; 개체의 참조 횟수를 감소시켜 해제되도록 한다:
ObjRelease(address)

Generally each new copy of an object's address should be treated as another reference to the object, so the script should call ObjAddRef() when it gains a copy and ObjRelease() immediately before losing one. For example, whenever an address is copied via something like x := address, ObjAddRef() should be called. Similarly, when the script is finished with x (or is about to overwrite x's value), it should call ObjRelease().

Note that the Object() function can be used even on objects which it did not create, such as COM objects and File objects.