Skip to content
AutoHotKey
2011.02.21 23:58

autohotkey_L Object

조회 수 15387 추천 수 0 댓글 0
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄
?

단축키

Prev이전 문서

Next다음 문서

크게 작게 위로 아래로 댓글로 가기 인쇄

autohotkey_L Object



 objects.htm



ico-file

객체 Objects

부가적인 문자열이나 숫자들에 대하여, 오토핫키_L 은 객체들을 지원한다. 객체는 별개의 타입값으로 간주된다. 객체는 다른 자료형들처럼 변수에 담겨질 수 있고, 함수나 저장된 다른 객체에 의해 넘겨주거나 반환될 수 있다.

객체들은 참조형이다. food := pizza, 와 같은 정의 후에, food 는 실제의 pizza 객체를 참조할 수 있다. 유사하게, Eat(pizza) 는 실제의 pizza object 를 Eat() 함수로 넘겨줄 수 있는데, 이것은 실제 객체를 수정 할 수 있다. 만약 파라미터가 ByRef에 의해 선언되었다면, 저 함수는 단지 가리키는 주소값만 변경 할 수 있다.

IsObject 는 값과 객체를 판별 할 수 있다:

Result := IsObject(expression)

객체에는 현제 세가지 기본 종류가 있다:

  • Object - scriptable associative array. 구문화 가능한 연관 배열
  • File - provides an interface for file input/output. 파일 인풋/아웃풋 인터페이스 제공
  • ComObject - wraps an IDispatch interface (a COM or "Automation" object). IDispatch 인터페이스로 둘러쌓여있음

문법 Syntax

지원되는 연산자

객체에 지원되는 연산자는 다음으로 제한되어 있다:

  • 직접비교(= == != <>)는 객체를 참조로 비교한다. 객체는 비객체 값과 절대 같을 수 없다.
  • (&object)의 주소는 native 인터페이스의 포인터를 반환한다 - 생명 주기 동안 유니크하게 구별 가능하다. Reference Counting 부분을 참고 하라.
  • 삼항연산(a ? b : c) : 어느 피연산자도 객체가 될 수 있다.
  • 참인지 거짓인지 판별하기 위한 표현식에서, 객체들은 true 로 간주된다.
  • 점과 괄호는 다음 섹션에서. Dot and brackets work as outlined in the next section.
  • Func(Params*)은 객체로 된 Params을 요구한다.

지원되지 않는 연산자는 객체를 빈 문자열로 간주한다.

객체 호출 Invoking an Object

기능을 호출하기 위해서 두가지 기본적인 문법의 형태가 가능하다: Object[Params] and Object.Param. 두 경우 모두, Object 는 변수이거나 객체에 값을 매기는 더 복잡한 표현식일 수 있다. 각각의 경우에, 실제 연산의 결과는 객체에 정의되어 있다. 보통 연산의 SET은 정상적으로 저장된 값을 반환한다.

객체[파라미터]

괄호는 보통 객체-연산을 의미하고, 그것의 파라미터 목록의 범위를 정한다.

Value  := Object[ Params ]             ; GET
Result := Object[ Params ] := Value    ; SET
Result := Object[ Param  ]( Params )   ; CALL

호출은 괄호안에 하나의 파라미터만을 지원한다. 보통 이것은 객체안에 저장하여 호출하기 위한 함수명이다. 더 많은 정보는 Arrays of Functions 을 보라.

객체.파라미터

첫번째 파라미터는 순수하게 글자와 숫자와 언더스코프로 구성되어 있고, 대안적인 문법이 사용가능하다. When the first parameter is a known (literal) value consisting purely of alphanumeric characters and/or underscore, alternate syntax is available:

Value  := Object.Param             ; GET
Result := Object.Param := Value    ; SET
Result := Object.Param( Params )   ; CALL

비록 하나의 문자열 파라미터만 이 방법으로 명시될 수 있지만, 추가적인 파라미터들도 명시될 수 있다:

Value  := Object.Param1[ Param2, Param3, ... ]            ; GET
Result := Object.Param1[ Param2, Param3, ... ] := Value   ; SET
Result := Object.Param1( Param2, Param3, ... )            ; CALL

삽입어구들은 일반적으로 괄호로 분리되지만, (:=): 연산자의 경우에는 그렇지 않을 수도 있다.

Result := Object.Param1( Param2, Param3, ... ) := Value   ; SET

점이 표현식의 앞에 있거나 + 나 * 과 같은 연산자에 선행할때, 그것은 무조건적으로 부동소수점 기수법으로 취습된다. 만약 점이 공백 뒤에 있다면, 그것은 연결 연산자로 취급된다; 만약 공백이 선행하지 않는다면 에러가 발생한다. 그외 다른 인용되지 않은 점은 (.= 을 포함하여) 객체 접근 연산자로 취급된다.

Reference-Counting

스크립트들은 내부적으로 기본 참조-카운팅 메커니즘을 이용하여 , 더이상 참조하지 않는 객체들에 대하여 자동적으로 리소스를 해제한다. 스크립트 작성자는 이 메커니즘을 명쾌하게 언급할 필요는 없다. 관리되지 않는 포인터들을 직접적으로 다룰 때를 제외하고는 말이다. 더 많은 정보를 원한다면, ObjAddRef 를 보라.

; 살려두기 위해 객체의 참조카운트를 증가시킨다 : 
ObjAddRef(address)
...
; 해체시키기 위해 객체의 참조카운트를 감소시킨다 :
ObjRelease(address)

그러나, ObjAddRef 는 Object(obj) 를 통해서 초기화시에 주소값을 저장 할 때 사용될 필요가 없다.

코드를 실행할 때, 객체의 마지막 참조가 지속되고 있을 경우 __Delete 메타-함수를 시행하라.

알려진 제약사항:

  • 주기적인 참조들로 말미암은 제약 - 객체에 대한 참조가 존제하면 객체는 해제될 수 없다. 스크립트가 최종적으로 객체에 접근 할 수 없다 해도.
  • static 변수와 전역변수들이 프로그램 종료시에 자동적으로 지속될 된다해도, non-static 지역변수들이나 임시 저장공간 표현식 판정이 아닐 동안에도, 만약 함수나 표현식이 완전히 허용되었 을 때, 이 참조들은 지속된다. Although references in static and global variables are released automatically when the program exits, references in non-static local variables or in temporary storage during expression evaluation are not. These references are only released if the function or expression is allowed to complete normally.

객체로부터 이용된 메모리가 프로그램 종료시 재요청 된다해도 , 모든 객체에 대한 참조가 해제되지 않는다면 __Delete 는 호출되지 않을 것이다. 임시파일처럼, OS에 의해 자동적으로 재요청되지 않는 다른 리소스들을 해제 할 때 이것은 중요하다.

Object()

scriptable object 를 생성한다. 그것은 연관배열이기도 하다.

Object := Object()
Object := Object(Key1, Value1 [, Key2, Value2 ... ])

파라미터들은 쌍을 이루어야 한다. single-parameter mode 일 때만 빼고.

; 명확한 초기화:
obj := Object()
obj[a] := b
obj[c] := d

; 간략한 초기화:
obj := Object(a, b, c, d)

연관 배열

각 객체는키-값 의 쌍을 가진 리스트를 포함한다 - 각각의 키, 값은 문자열이나 숫자 또는 객체이다. 아래와 같이 값들은 키에 의해 객체에 저장되거나 검색된다:

array := Object()
array["a"] := "alpha"
array["b"] := "bravo"
MsgBox % array["a"] . " " . array["b"]

물론 점을 이용한 문법이 이용될 수도 있다. 이전의 예제에 내용을 추가한다:

array.a := "apple"
MsgBox % array.a . " " . array.b

키-값 쌍을 삭제하기 위해서, _Remove 를 이용한다.

정수 키는 native 32-bit 문자 형식으로 저장되고, 그렇기 때문에 -2147483648 에서 2147483647 사이가 된다. 이 범위를 벗어난 정수는 묶여진다; key << 32 >> 32 은 비슷한 효과를 낸다. 형식이 무시된 인스턴스 x[0x10], x[16] 그리고 x[00016] 은 동일하다. 키는 연속적이지 않아도 된다 - x[1]x[1000] 의 값을 갖는 객체 x 는 실제로 오직 두개의 키-값 쌍을 가진다.

실수는 키로 지원되지 않는다 - 그것이 문자열로 변환되지 않는 한. 실수는 정확히 있는그대로 유지되지 않는다, pure floating-point numbers (such as the result of 0+1.0 또는 Sqrt(y)) 는 current float format 의 형태로 끼워맞춰진다.

Note: "base" 키는 특별한 의미를 지니고 있다. _Insert 와 쓰일 때만 빼고.

Built-in Methods

See Object.

Arrays of Arrays

객체가 다른 객체를 포함할 수 있으므로, 배열의 배열 역시 가능하다. 예를 들어, table 은 열들의 배열이고, 각 열은 칼럼들의 배열이다. 열 x 의 칼럼 y 의 내용은 아래의 방법을 이용하여 set 할 수도 있다:

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

만일 table[x] 가 없으면, AB 는 두가지 의미로 다르다:

  • A 가 실패하는데 반하여 B 는 자동적으로 객체를 생성하고 table[x]에 저장한다.
  • 만일 tablebasemeta-functions 을 정의한다면, 그들은 다음과 같이 호출된다:
    table.base.__Get(table, x)[y] := content   ; A
    table.base.__Set(table, x, y, content)     ; B
    결과적으로, B 는 객체에 custom behaviour 를 정의하기 위해 사용된다.

Arrays of Functions

비록 실제 함수 참조가 아직 지원되지 않지만 , 함수의 배열은 객체의 함수명을 저장하는 것으로 시뮬레이트 할 수 있다. 만약 array[index] 가 함수명을 포함한다면, 다음의 두 예시는 동등하다:

array[index](param)
n := array[index]
%n%(param)

만일 index 가 the name of a built-in method 를 포함한다면 - base object 또는 함수를 표현하는 객체 - 오직 첫번째 예시만 작동할 것이다.

알려진 제약사항:

  • 만약 객체호출이, 실행중인 함수나 서브루틴의 stack에 존재한다면, Exit 는 객체-호출이 신규 쓰레드를 생성하는 것처럼 작동한다; 즉, 호출자에게 즉시 반환된다. 그러나, Exit 는 적절한 시기에 종료 명령을 내린다.
  • 현재 x.y[z]()x["y", z]() 이와 같이 취급되는데, 이것은 지원되지 않는다. 두번째 해결책으로, `(x.y)[z]() 는 먼저 x.y 가 값을 가지도록 하고, then the result used as the target of the object-call. Escaping the 괄호열고 (`() allows the expression to be used at the beginning of a line, where the parenthesis would otherwise begin a continuation section.

Object( address | object )

객체 참조로부터 인터페이스 포인터를 찾거나 그 반대로 객체 참조를 찾는다. 이것은 고급 특성이며 너가 really 뭘하고 있는지 안다면 사용해도 된다; Reference Counting 부분을 살펴보라. A_EventInfo 를 통해 callback을 가진 함수와 연결하는 것에 한가지 중요한 방법이 있다.

address := Object(object)
object := Object(address)

각각의 경우에 object 의 reference-count 는 자동적으로 증가되어 객체가 초기에 해제되진 않는다. 일반적으로 주소 복사는 객체 참조로 간주되며, ObjAddRefObjRelease 를 호출하는 구문에 한해서만 제외된다. 예를 들면, x := address 대신에 consider using ObjAddRef(x := address) and ObjRelease(x) when finished with x if address may be released in the mean-time. This ensures the object is freed when the last reference to it is lost - and not before then.

Note that this function applies equally to objects not created by Object(), such as COM object wrappers or File objects.

확장성 Extensibility

객체의 연산은 특별한 base 메커니즘에 따라 수정되거나 확장될 수 있다. 새로운 연산의 키는 객체의 base 를 호출한다. 기본 base 객체가 호출 될 때, 이것은 다음을 따른다:

  • __Get, __Set 또는 __Call 메타-함수를 호출한다.(있을 때만) 만약 메타-함수 실행와중 return과 맞닥드린다면, 더이상 처리가 일어나지 않는다. 모든 연산들에 대하여, 반환값은 표현식의 결과로 사용된다.

    Set: 연산이 성공적이었다면, __Set 은 기존의 r-value와는 다른 새로운 필드의 값을 반환한다. 이것은 다음과 같은 연결된 연산을 허용한다 a.x := b.y := z. 만약 임무를 실패한다면 빈 문자열이 반환되어야 하며 오류를 감지하는 구문을 따른다.

  • If this is a get or call operation, search for a matching key in the base object's own fields.

    Get: 만약 발견되면, 필드 값은 반환된다.
    Call: 만약 찾으면, 필드는 대상객체(첫번째 파라미터)와 함께 called 된다.

  • 반복적으로 그것의 base 객체를 호출한다. 이것은 그것의 base의 base의 base 로부터 반복해서 객체가 상속되는 속성을 따른다.

만약 연산을 제어하는 base 객체가 없으면, 처리는 평소처럼 계속될 것이다:

  • Get: 키가 "base" 이면 객체의 base가 검색된다.
  • Set: 키가 "base" 이면 객체의 base가 set된다; 비 객체 값은 존재하는 base를 삭제시켜버린다.

    반면 새로운 키-값 쌍은 객체안에서 생성되어 저장된다.

  • Call: A built-in method 가 호출될 것이다.

예시: 단순 값 상속.

Color := Object("R", 0, "G", 0, "B", 0)
blue := Object("B", 255, "base", Color)
cyan := Object("G", 255, "base", blue)
MsgBox % "blue: " blue.R "," blue.G "," blue.B
MsgBox % "cyan: " cyan.R "," cyan.G "," cyan.B

예시: 위의 예시에 덧붙여, 혼합된 RGB 값을 찾기 위한 함수를 정의한다.

Color.GetRGB := "Color_GetRGB"

MsgBox % cyan.GetRGB()  ; Displays 65535.

Color_GetRGB(clr) {
    return clr.R << 16 | clr.G << 8 | clr.B
}

메타-함수 Meta-Functions

메타-함수는 객체가 어떻게 동작해야할지를 명확히 정의하는 스크립트를 따른다. 호출되었을 때, 파라미터 리스트는 대상 객체의 참조값을 포함하고, get, set 또는 call 연산의 파라미터들이 그 다음에 온다. 만약 함수가 값을 반환하면, 그것은 연산의 결과로 사용되며 더이상 처리는 일어나지 않는다. 만약 return이 일어나지 않으면, 상단에 묘사한 규칙에 따라 처리가 계속된다.

예제: 위의 예시를 다시 시행하여 단일 RGB 값을 저장하도록 한다.

Color := Object("RGB", 0
                , "__Set", "Color_Set"
                , "__Get", "Color_Get")

blue := Object("base", Color, "B", 255)
cyan := Object("base", Color, "RGB", 0x00ffff)

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

Color_Get(clr, name)
{
    if name = R
        return (clr.RGB >> 16) & 255
    if name = G
        return (clr.RGB >> 8) & 255
    if name = B
        return clr.RGB & 255
}

Color_Set(clr, name, val)
{
    if name in R,G,B
    {
        val &= 255
        
        if      name = R
            clr.rgb := (val << 16) | (clr.rgb & ~0xff0000)
        else if name = G
            clr.rgb := (val << 8)  | (clr.rgb & ~0x00ff00)
        else  ; name = B
            clr.rgb :=  val        | (clr.rgb & ~0x0000ff)
        
        ; 'Return' must be used to indicate a new key-value pair should not be created.
        ; This also defines what will be stored in the 'x' in 'x := clr[name] := val':
        return val
    }
}

Object()가 파라미터와 함께 호출될 때, 키-값 쌍은 왼쪽에서 오른쪽 방향으로 들어가 있어야 한다. 아래의 예처럼 B가 base 앞에 있다면, 새로운 키-값 쌍이 생성되고 "RGB" 는 수정되지 않는다:

c := Object("B", 255, "base", Color)

이 행위는 메타-함수를 우회하거나, base 가 특정키에 의해 요청되지 않을 때 사용된다.

알려진 제약사항:

  • 만약 return 이 파라미터 없이 사용된다면, 이것은 return "" 과 같고, 그러므로 메타-함수로부터 "escape" 하기 위해 사용될 수 없다. 예를 들어:

    ; 잘못된 방법:
    x_Set(x, name, value)
    {
        if name != foo
            return  ; This prevents any new key-value pairs from being created, including real_foo.
        ;...
        return x.real_foo := modified_value
    }
    
    ; 올바른 방법:
    x_Set(x, name, value)
    {
        if name = foo
        {
            ;...
            return x.real_foo := modified_value
        }
    }
  • See also Exit limitation.

Cleanup

만일 객체가 다음의 자원과 연관이 있다면 - DllCall에 의해 메모리를 점유하는 파일이나 네트워크 핸들 또는 포인터 - 이 자원들은 __Delete meta-function 의 시행으로 자동적으로 관리된다. 예를 들어:

; Create an object with a __delete meta-function:
obj := Object("base", Object("__Delete", "MemoryObject_Delete"))
; Allocate a resource and store it in the object:
obj.ptr := DllCall("GlobalAlloc", "uint", 0, "uint", 1024)

MsgBox % "Memory allocated: " obj.ptr

MemoryObject_Delete(obj)
{   ; Retrieve the resource.
    if p := obj.ptr
    {   ; It hasn't already been freed, so free it.
        DllCall("GlobalFree", "uint", p)
        ; Set to zero so we know it has been freed.
        obj.ptr := 0
    }
    MsgBox % "Memory freed: " p
}

When the last reference to obj is about to be released, obj.base.__Delete is called with obj as the first parameter. Note that at the time __Delete is called, all other references to the object have been released. If there are still references to the object after the function returns (for instance, because the function copied a reference into a static or global variable), the object is not deleted and __Delete may be called again at a later time.

Arrays of Arrays

When a multi-parameter assignment such as table[x, y] := content implicitly causes a new object to be created, the new object ordinarily has no base and therefore no custom methods or special behaviour. __Set may be used to initialize these objects, as demonstrated below.

x := Object("base", Object("addr", "x_Addr", "__Set", "x_Setter"))

; Assign value, implicitly calling x_Setter to create sub-objects.
x[1,2,3] := "..."

; Retrieve value and call example method.
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] := Object("base", x.base)
}

x_Addr(x) {
    return &x
}

Since x_Setter has four mandatory parameters, it will only be called when there are two or more key parameters. When the assignment above occurs, the following takes place:

  • x[1] does not exist, so x_Setter(x,1,2,3) is called ("..." is not passed as there are too few parameters).
    • x[1] is assigned a new object with the same base as x.
    • No value is returned – the assignment continues.
  • x[1][2] does not exist, so x_Setter(x[1],2,3,"...") is called.
    • x[1][2] is assigned a new object with the same base as x[1].
    • No value is returned – the assignment continues.
  • x[1][2][3] does not exist, but since x_Setter requires four parameters and there are only three (x[1][2], 3, "..."), it is not called and the assignment completes as normal.

Objects as Functions

When a call such as obj.func(param) is made, obj.func may contain a function name or an object. If obj.func contains an object, it is invoked using obj as the key. In most cases obj.func[obj] does not exist and obj.func's __Call meta-function is invoked instead. This may be used to change the behaviour of function calls in an abstract way, as shown in the example below:

FuncType := Object("__Call", "FuncType_Call")
func := Object("base", FuncType, 1, "One", 2, "Two")
obj := Object("func", func, "name", "foo")
obj.func("bar")  ; Shows "One foo bar", "Two foo bar"

FuncType_Call(func, obj, param) {
    ; Call a list of functions.
    Loop % func.MaxIndex()
        func[A_Index](obj, param)
}

One(obj, param) {
    MsgBox % A_ThisFunc " " obj.name " " param
}
Two(obj, param) {
    MsgBox % A_ThisFunc " " obj.name " " param
}

Default Base

When a non-object value is used with object syntax or passed to ObjGet, ObjSet or ObjCall, the default base object is invoked. This can be used for debugging or to globally define object-like behaviour for strings, numbers and/or variables. The default base may be accessed by using .base with any non-object value; for instance, "".base. Although the default base may not be set as in "".base := Object(), the default base may itself have a base as in "".base.base := Object().

Example 1: Automatic Var Init

When an empty variable is used as the target of a set operation, it is passed directly to the __Set meta-function, giving it opportunity to insert a new object into the variable. Limitations: Since the first parameter must be declared ByRef, the meta-function will not be invoked if the target is not a variable. This example does not support multiple parameters.

"".base.__Set := "Default_Set_AutomaticVarInit"

empty_var.foo := "bar"
MsgBox % empty_var.foo

Default_Set_AutomaticVarInit(ByRef var, key, value)
{
    if var =
        var := Object(key, value)
}

Example 2: Pseudo-Properties

Object "syntax sugar" can be applied to strings and numbers.

"".base.__Get := "Default_Get_PseudoProperty"
"".base.is  := "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
}

Note that built-in functions may also be used, but in this case the parentheses cannot be omitted:

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

Example 3: Debug

If allowing a value to be treated as an object is undesirable, a warning may be shown whenever a non-object value is invoked:

"".base.__Call := "Default__Warn"
"".base.__Set  := "Default__Warn"
"".base.__Get  := "Default__Warn"

empty_var.foo := "bar"
x := (1 + 1).is("integer")

Default__Warn(nonobj, p1="", p2="", p3="", p4="")
{
    ListLines
    MsgBox A non-object value was improperly invoked.`n`nSpecifically: %nonobj%
}


로그인 후 댓글쓰기가 가능합니다.

?

List of Articles
번호 분류 제목 날짜 조회 수
46 컴퓨터잡담 AHK_L) SysListView321 컨트롤 내용 추출하기 2011.10.07 9888
45 컴퓨터잡담 autohotkey - 변수리스트(Variables and Expressions) 모음 2011.09.30 11830
44 AutoHotKey [AHK_B&AHK_L] 익스플로러 HTML 문서정보 알아내기(IE HTML Element Spy) 2011.08.08 15160
43 AutoHotKey [AHK_B&AHK_L] 엑셀 제어 비교. 2 2011.08.02 20252
42 AutoHotKey [AHK_L] 현재 열려진 인터넷 창 값 가져오기 4 2011.08.02 16574
41 컴퓨터잡담 [AHK] COM Standard Library 1 1 2011.07.28 13588
40 컴퓨터잡담 [AHK] AutoHotkey_N, AutoHotkey.dll 1 2011.07.28 13562
39 AutoHotKey 정보수집 2011.03.30 16064
38 AutoHotKey [ahk]웹페이지가 띄워진 창 내용을 추출하여 로딩이 완료되었는지를 확인할 수 있는 소스 2011.02.25 14747
37 AutoHotKey ahk로 만든 파일을 exe로 컴파일 한 후 실행시킬때 변수를 임의 1 1 2011.02.24 15310
36 AutoHotKey 클릭해서 새창열리는 페이지에 클릭 또는 값설정 가능한가요? 2011.02.22 14723
35 AutoHotKey ahk_l 웹페이지 파일로 저장한 뒤 불러와 필요한 부분 추출하여 출력하기 2011.02.22 16988
34 AutoHotKey ahk_l 웹페이지 앞, 뒤페이지 제어 예제소스 및 설명첨부 2011.02.22 17535
33 AutoHotKey ahk_l 과 com 의 이해 2011.02.22 17485
» AutoHotKey autohotkey_L Object 2011.02.21 15387
31 AutoHotKey COM 사용 1 2011.02.21 19063
30 AutoHotKey 웹페이지의 내용을 변수에 넣기 2011.02.17 14492
29 AutoHotKey WinMenuSelectItem로 메뉴선택하기 1 2011.02.17 16252
28 AutoHotKey [ahk_l] 섬세한 인터넷 자동검색 2011.02.16 18182
27 AutoHotKey [ahk] 다른 프로그램의 트레이 아이콘을 숨기기 1 4 2011.02.16 19005
Board Pagination Prev 1 2 3 4 5 Next
/ 5

http://urin79.com

우린친구블로그

sketchbook5, 스케치북5

sketchbook5, 스케치북5

나눔글꼴 설치 안내


이 PC에는 나눔글꼴이 설치되어 있지 않습니다.

이 사이트를 나눔글꼴로 보기 위해서는
나눔글꼴을 설치해야 합니다.

설치 취소