요즘 바쁜 와중에도 짬 내서 구글의 Go 언어 공부하느라 더 정신은 없지만 재밌네요
하지만 아직까지도 저는 오토핫키를 가장 선호합니다, 처음 배운 언어고 가장 많이 사용하고 있어서 그런지...
인터프리터 언어의 일인자(?)인 파이썬보다도 더 자주 사용하는 거 같네요

 

클래스에 대한 글을 작성하려 했는데, 먼저 함수에 대해 충분히 알아야 할 것 같아서 이 글을 먼저 적게 되었습니다
함수를 사용하긴 하더라도, 제대로 활용하는 사람은 드문 것 같아서요

 

사실, 저도 잘 모릅니다;;
하지만, 더 배우고자 한다면 남을 가르치는 것이 도움이 된다고 했나요?
제가 함수에 대해 아는 것을 적어보려고 합니다

 

안내
- 저는 오토핫키를 포함한 다른 프로그래밍 언어를 모두 원어로 배워서, 한글 명칭을 잘 모릅니다... 대부분 영어로 적습니다
- 예제를 직접 실행해보세요, 이해에 많은 도움이 될 겁니다

 

Function 함수

 

일단 함수를 왜 사용하는지 알아야 겠죠, 함수를 사용하는 주된 이유는

  1. 코드의 유지 보수를 수월하게 하고
  2. 코드의 범용성을 높이고
  3. 생산성이 높아지기 떄문입니다

함수를 사용하는 이유가 goto, gosub 의 사용을 권장하지 않는 이유이기도 합니다
한두 개라면 몰라도 서브루틴에 대부분 goto를 사용한다면 코드가 굉장히 지저분(...) 하고
범용성이 제로에 가까워서, 새로 코드를 작성할 때 이전의 코드가 전혀 도움이 안 되죠

물론 대충 후다닥 만들때는 그냥 goto 로 만드는게 편하죠, 나중까지 생각한다면 함수로 만드는게 좋고요


Function (이하 함수)는 goto, gosub 과 같은 서브루틴(subroutine)의 기능에 추가로 파라미터(입력)을 받아 올 수 있습니다
그리고, 값 (value)을 리턴(Return) 할 수도 있죠.
아래 '더하기' 함수는 x, y 파라미터를 받아와 각각 더하고 합을 리턴합니다

 

더하기(x, y)
{
    return x + y
}

 

'더하기'는 함수 이름이고, 괄호 안에 들어가는 x 와 y는 파라미터라고 부릅니다


이렇게 함수를 만드는 것을 Function Definition (함수 정의)라고 하는데요

 

'더하기' 함수를 사용하려면 반드시 x, y 파라미터에 해당하는 각각의 값을 보내줘야 하고
리턴되는 값을 변수에 저장하려면 ' := ' 오퍼레이터를 사용해야 합니다, 아래처럼요

 

합 := 더하기(10, 5) ; '합' 변수에는 '15'가 저장됩니다

 

값을 저장하지 않고, 아래처럼 그냥 함수만 사용할 수도 있습니다

 

더하기(10, 5)

 

그런데 '더하기' 함수에는 값을 리턴하는 외의 어떠한 명령도 없기 때문에 위 코드는 아무런 의미가 없죠
하지만 명령어(Command)를 사용할 때에는 유용합니다
변수로 저장해두고 계속 사용하지 않을 거라면, 아래처럼 사용하면 되죠

 

MsgBox % "3 더하기 2 는: " . 더하기(3, 2) ; ' 3 더하기 2는: 5 '

 

함수는 파라미터를 포함해 모든 표현을 *익스프레션으로 표현합니다

익스프레션: 변수, % 변수, :=, "문자"

 

따라서, 변수를 넘길 때는 따옴표("") 처리 없이, 문자를 넘길 때는 "문자 "로 표현해야 합니다

 

함수(변수, "문자 ")

 

Parameters 파라미터

 

함수명(첫 번째 파라미터, 두 번째 파라미터, 세 번째 파라미터, ...) 과 같은 형식으로 함수를 정의합니다
파라미터는 필수가 아니에요, 생략하고 함수명()로 정의할 수도 있습니다

 

ByRef 파라미터
함수가 받아온 파라미터는 기본적으로 로컬(Local) 변수로 사용됩니다, 하지만 *ByRef 파라미터로 받아온다면 그렇지 않죠

ByRef: By Reference의 약자, 참조한다는 뜻

 

무슨 말인지 모르시겠다고요?, 아래의 예를 읽다 보면 이해가 될 거예요 :)

 

Swap(ByRef x, ByRef y)
{
    save := x
    x := y
    y := save
}

 

Swap 함수는 ByRef 파라미터의 사용으로 파라미터 x, y를 *Alias로 만듭니다

Alias: 이후에 작성할 멀티 스레드 글에서 자세히 알아보겠습니다, 지금은 그냥 이런 게 있구나 정도만 생각하세요

 

쉽게 말하면, ByRef를 사용함으로써
파라미터 x, y는 Swap 함수 내에서도, 코드 전체에 걸쳐서도 같은 데이터를 메모리에서 참조(Refer) 하게 되는 거죠
따라서 Swap 함수로 교환된 값을 함수 밖에서도 사용할 수 있습니다

 

만약에 ByRef를 사용하지 않는다면
함수 내에서는 x, y 값을 교환해서 저장하지만, 함수 밖에서는 아무런 변화가 없습니다
따라서, 코드 전체에서는 실제로 교환된 값을 사용할 수는 없습니다

 

말이 점점 어려워지네요, 역시 코드가 제일 쉽죠?

 

; ByRef 의 경우
a := 10
b := -10
MsgBox % "a : " a "`nb : " b ; a는 10, b는 -10
Swap(a, b) ;a 변수의 값은 b로, b변수의 값은 a에 저장됨

MsgBox % "a : " a "`nb : " b ; a는  -10, b는 10 로 값이 교환됨

 

Swap(ByRef x, ByRef y){
    save := x
    x := y
    y := save
}

 

; ByRef 가 없다면
a := 10
b := -10
MsgBox % "a : " a "`nb : " b ; a는 10, b는 -10
Swap(a, b)
MsgBox % "a : " a "`nb : " b ; 여전히 a는  10, b는 -10

 

Swap(x, y){
    save := x
    x := y
    y := save
}

 

읽기만으로 이해가 잘 되지 않는다면 코드를 실행해보고 결과를 확인해보세요

 

ByRef 의 장점은 여러 개의 값을 *리턴하는 효과를 가져온다는 거죠, 위 코드에서는 x, y 두 개의 값을 리턴했다고 보면 됩니다
반면에, 리턴(Return)으로는 한 개의 값만 리턴이 가능해요

리턴하는 효과?: 엄밀히 따지면, ByRef는 값을 리턴하는 게 아니라 값을 설정한다고 봐야 하기 때문

 

ByRef의 또 다른 장점으로는,
사이즈가 큰 문자열을 리턴할 때 return String 을 사용하는 것보다 속도가 빠르다는 점이 있습니다
웹페이지 소스나, json 파싱을 할 때 유용할 것 같지만,
속도 차이는 벤치마킹상이지, 사람이 체감하는 속도는 아니라서 막상 사용은 잘 안 합니다


옵션 파라미터

함수를 호출할 때마다 모든 파라미터에 값을 넘기려니 귀찮은 때가 많습니다
그럴 때, 파라미터에 기본값을 설정해두면 편하죠, 이러한 파라미터를 Optional Parameter (옵션 파라미터)라고 불러요

 

더하기(x, y, z := 0)
{
 return x + y + z
}

 

더하기 함수의 3번째 파라미터 z는 기본값이 0으로 설정된 옵션 파라미터입니다
파라미터를 받아오지 않는 경우에는 0으로 설정되고, 받아온 경우에는 받은 값을 사용합니다
따라서, 더하기(5, 5) 의 경우에는 10, 더하기(5, 5, 5) 의 경우에는 15 가 리턴됩니다

 

다시 한번: 함수는 모든 표현을 익스프레션으로 한다고 했습니다, 그래서 z = 0 이 아닌, z := 0 을 사용하죠
그럼에도 불구하고 아직까지는 베이직 버전에서 이동한 개발자를 위해 ' = ' 도 허용됩니다


Variadic 파라미터

파라미터의 개수를 유동적으로 설정하고 싶은 경우가 많습니다, 특히 연산 작업을 할 경우에는 더욱 그렇죠
파라미터 이름 뒤에 * 처리를 하면 입력받은 파라미터의 개수만큼, 파라미터가 자동으로 설정됩니다

 

MsgBox % 더하기(10) ; 10
MsgBox % 더하기(10, 10) ; 20
MsgBox % 더하기(10, 10, 10) ; 30

 

더하기(params*) {
    for i,p in params
  Sum += p
    return Sum
}

 

리턴 Return

 

위에서 잠깐 설명했듯이 함수는 리턴 값을 호출자(Caller)에게 보내거나/저장할 수 있습니다

 

Test := returnTest() ; 변수 Test에 리턴값 123 저장
MsgBox, % Test

 

returnTest() {
  return 123
}

 

return 은 한 개의 값만 리턴할 수 있는 반면에 ByRef를 사용하면 여러 개의 값을 리턴하는 효과가 있다고 했죠?
오브젝트나 배열을 생성해 리턴해도 동일한 효과를 얻을 수 있습니다

 

Test := returnArray()
MsgBox, % Test[1] "`, " Test[2] ; A , B

 

returnArray()
{
 Test := ["A", "B"]
 return Test
}

 

로컬 변수 Local Variables

 

기본적으로 함수 내에서 사용되고, 생성하는 변수는 모두 로컬 변수입니다, 오직 함수 내에서만 사용되는 거죠
함수 밖 변수, 다른 함수의 변수와는 아무런 관련이 없어서, 이름이 동일해도 상관없습니다

 

로컬변수 := 1234
Add := 더하기(5, 5)
Sub := 빼기(10, 5)
MsgBox, % "함수 밖 로컬변수 - " 로컬변수 "`n더하기(5, 5) - " Add "`n빼기(10, 5) - " Sub

 

더하기(X, Y){
 로컬변수 := X + Y
 return 로컬변수
}

 

빼기(X, Y){ ; 파라미터가 X, Y 로 동일해도 상관없음
 로컬변수 := X - Y ; 이름이 동일해도 상관없음
 return 로컬변수
}

 

글로벌 변수 Global Variables

 

함수 밖에서도 변수를 사용하려면 아까 설명한 ByRef 을 이용할 수도 있고
함수 내에서 변수의 레벨을 global로 설정하면 됩니다

 

글로벌변수 := 111
MsgBox % 글로벌변수 ; 111
Func()
MsgBox % 글로벌변수 ; 222

 

Func(){
 global 글로벌변수 ; 변수 '글로벌변수' 은 global 변수임
 글로벌변수 := 222
}

 

여러 개의 글로벌 변수를 설정하려면 global, 변수 1, 변수 2, 변수 3, ...으로
모든 변수를 글로벌 변수로 설정하려면 global 을 적어주면 됩니다

 

Func(){
 global 변수1, 변수2, 변수3, ... ; 변수1, 변수2, 변수3 을 글로벌변수로, 나머지는 로컬변수
 ...
}

 

Func(){
 global ;모두 글로벌 변수
 ...
}

 

스태틱 변수 Static Variables

 

스태틱 변수는 기본적으로 로컬 변수지만, 추가로 함수 내에서 계속해서 값이 기억된다는 점이 특징입니다
무슨 말인지 코드로 확인해볼까요?

 

일단, 로컬 변수부터 확인해보죠

 

MsgBox % 누적(1) ; 1, 누적안됨
MsgBox % 누적(10) ; 10, 누적안됨
MsgBox % 누적(100) ; 100 , 누적안됨

 

누적(X){
 aug += X
 return aug
}

 

숫자가 누적이 되지 않습니다, 위 코드에서 누적 함수의 aug 변수를 스태틱 변수로 설정하면 어떻게 될까요?

 

MsgBox % 누적(1) ; 1
MsgBox % 누적(10) ; 11
MsgBox % 누적(100) ; 111

 

누적(X)
{
 static aug ; 스태틱 설정
 aug += X
 return aug
}

 

숫자가 계속 누적돼서 나옵니다, 이처럼 스태틱 변수는 함수 내에서 값이 기억되어 사용되는 변수입니다
그래서 처음 1 이 기억되고, 거기다가 10을 더하고, 마지막으로 100을 더하는 거죠

 

여러 개의 스태틱 변수를 설정하려면 static, 변수 1, 변수 2, 변수 3, ...으로
모든 변수를 스태틱 변수 설정하려면 static 을 적어주면 됩니다

 

Func(){
 static 변수1, 변수2, 변수3, ... ; 변수1, 변수2, 변수3 을 스태틱변수로, 나머지는 로컬변수
 ...
}

 

Func(){
 static ;모두 스태틱 변수
 ...
}

 

글로벌 변수와 스태틱 변수를 구분해서 설정하려면 아래처럼 하시면 됩니다
global 글로벌 변수 1, 글로벌 변수 2, ...
static 스태틱 변수 1, 스태틱 변수 2, ..

 

함수 호출 Calling

 

함수 내에서도 함수를 호출할 수 있습니다, 예를 들어

 

ParentFunc()

 

ParentFunc(){
 return ChildFunc()
}

 

ChildFunc(){
 MsgBox, 안녕!
}

 

위 코드는 너무 간단해서 굳이 이렇게 쓸 필요는 없어 보입니다.
실제로 활용될 수 있는 아래 코드를 확인해볼까요?

 

Wait_img("img.bmp", true) ;이미지 찾을시 해당 이미지를 클릭하게한다

ClickFunc(1000, 500) ; 좌표 1000, 500 클릭

 

Wait_img(img, Click := false){
 while !ErrorLevel = 0 ; 이미지를 찾을 때까지
  ImageSearch, vX, vY, 0, 0, A_ScreenWidth, A_ScreenHeight, % A_ScriptDir . "\" img
 return ((Click == true) ? ClickFunc(vX, vY) : 0)
}

 

ClickFunc(X, Y){
 Click %X% %Y%
}

 

한 함수에 몰아넣으면 될 텐데 왜 이렇게 사용하냐고 물으신다면,
반복되는 서브루틴을 개별 함수로 나눠서, 각각의 함수의 범용성을 높이기 위해서죠. 코드도 단축되고요

 

((Click == true) ? ClickFunc(vX, vY) : 0) 가 뭐죠? c언어의 Ternary Operator 입니다, 오토핫키도 c로 작성된 언어라 모든 연산자와 표현식은 c 하고 똑같습니다
모르셨다면 if, else를 한 문장으로 축약하는 표현식이라고 알아두시면 됩니다
Click 파라미터가 true라면 이미지를 클릭하고, false 면 클릭하지 않습니다

 

함수 객체 Func Object

 

프로그래밍에 대해 관심을 가졌다면, 언젠가 객체지향언어라는 것을 들어보았을 겁니다
C++, C# 그리고 자바 등 요즘 사용되는 언어는 대부분 객체지향언어인데요
객체지향프로그래밍의 이유는 점점 프로그램의 규모가 커지고, 개인보다는 집단이 개발하는 오픈소스 소프트웨어가 많아지기 때문이기도 합니다
객체로 나눠서 작성하면 유지 보수가 수월하고, 다른 개발자가 코드 개발에 참여하기도 쉽죠

 

그리고 객체지향언어에서 객체를 동적(Dynamic)으로 컨트롤하는 언어를 프로토타입 지향 언어라고 하는데
이와 같은 언어로는 대표적으로 자바스크립트가 있고, 오토핫키가 있습니다(!!!)

이 부분은 나중에 작성할 클래스 글에서 다루도록 하고, 일단은 함수 객체로 바로 넘어가죠 사실, 지식이 바닥남

 

함수 객체 (Func Object)는 함수 참조 Func Reference로 더 많이 불리는 거 같은데, 후자가 더 의미에 가까운 것 같습니다
말 그대로, 변수가 함수를 참조할 수 있게 하는 거죠. 해당 변수는 함수로의 포인터라고 이해해도 되고요

 

fn := Func("더하기") ; fn이 더하기 함수를 참조하도록 함
MsgBox % fn.Call(10, 10) ;함수 호출

 

더하기(X, Y)
{
 return X + Y
}

 

정말 신기하지 않습니까!!!?
이를 사용하면 라벨을 기본적으로 사용하는 셋타이머가 함수를 실행하게 할 수도 있습니다

 

#Persistent
fn := Func("알림")
SetTimer, % fn, 5000 ; 5초마다 알림 함수 호출

 

알림()
{
 MsgBox, 0, , 안녕, 3
}

 

사전에 파라미터를 설정해둘 수도 있습니다

 

#Persistent
fn := Func("알림").Bind("파라미터 바인드")
SetTimer, % fn, 5000 ; 5초마다 알림 함수 호출

알림(Txt)
{
 MsgBox, 0, , % Txt, 3
}


이후에는 클래스와 멀티 스레드에 대해 알아보도록 하겠습니다, 언제 작성할지는 지금은 모르겠네요

  1. 세준 2015.12.11 11:13 신고

    항상 글 잘 보구 있습니다. 다음에 포스팅하실 클래스가 궁금하네요 ^^ 오토핫키 L 이후부터는 도움말에는 정보가 넘 적은 거 같아서 혼자 익히기 어려움니 있네요..

  2. 구리 2015.12.28 20:50 신고

    좋은 강의 항상 감사합니다. 멀티 스레드 관련한 내용이 더욱 기다려지네요.^^ 함수에 관해서 약간 어렵고 거부감 들어서 잘 사용하지 않았는데 덕분에 스크립트도 깔끔하게 유지할 수 있게 될 것 같습니다. 감사합니다.

  3. 새우깡 2016.03.31 01:57 신고

    깔끔한 강좌 잘 보고 있습니다.
    프로그램에 대해서 아무것도 모르는 상태에서 오토핫키를 배워 업무에 아주 유용하게 사용하고 있습니다.
    간단한 코드로 시작해서 함수의 사용으로 코드를 줄였고
    이번에는 클래스를 사용해서 더 효율적인 코드를 짜고 싶은데...
    제가 이해, 활용할 수 있는 강좌 찾기가 쉽지 않네요.
    시간 되실때 클래스 강좌 업데이트 해주시면 감사하겠습니다.

    알찬 강좌 기대합니다!

    • BlogIcon 예지력 2016.05.04 16:17 신고

      정말 적고 싶은데, 일상에 치여 따로 글 작성할 시간이 안 나네요 주륵..

 

함수를 Try 하고, 예외발생시 에러코드를 Throw 하고 Exception 을 Catch 한다.

 

Try 는 알고리즘을 구성할때 굉장히 중요하고, 필수적인 문법입니다. 이미지서치할때 ErrorLevel 을 확인하죠?

에러레벨을 반환하는 과정이 바로 Try하고 Catch 하는 과정입니다.

 

기본적인 Try, Catch 코드를 확인해보겠습니다.

 

try  ; 함수를 시도한다
{
    HelloWorld()
    MakeToast()
}
catch e  ;try 함수의 Exception 을 관리합니다
{
    MsgBox, Exception 이 Throw 되었습니다 `nThrow된 함수: %e%
    Exit
}

HelloWorld()  ; 항상 성공하는 함수, Exception 이 없습니다
{
    MsgBox, 안녕하세요!
}

MakeToast()  ; 항상 실패하는 함수, Exception 이 생성됩니다
{
    ; try 부분의 Catch 함수로 점프
    throw A_ThisFunc " 가 실패하였습니다."

 

Try와 Catch 기본.ahk

 

Try  함수를 실행하는 부분입니다. 위 코드에서는 HelloWorld() 와  MakeToast() 라는 2개의 함수를 실행했습니다.

Throw : Exception 을 던져줍니다. 위 코드에서는 MakeToast() 함수가 Throw 했습니다.

Catch : Exception 즉 , 예외를 받습니다. Throw를 받아와 정보를 얻습니다. 위 코드에서는 A_ThisFunc " 가 실패하였습니다." 라는 예외를 받았습니다.

 

주석을 달았으니 알고리즘에 대한 이해는 어느정도 되실거라 예상합니다. 그러면 구체적으로 Try 와 Catch 에 대해 설명해볼게요

 

Try는 블럭 { } 으로 감싸주어야합니다.

 

try {
    ...
} catch e {
    ...
}

 

 

Catch 는 아래와 같습니다.

 

Catch [, 저장할변수]
{
     ...
}

 

저장할변수는 생략이 가능한데, 변수설정시에는 아래 5가지의 정보가 오브젝트로 저장됩니다.

What : 명령 또는 실행된 함수 또는 에러가 실행된 때에 대한 이름

File : 에러가 발생한 스크립트 파일의 경로

Line : 에러가 발생한 곳의 줄 번호

Message : 에러 메세지 또는 ErrorLevel 값

Extra : 에러에 대한 부가적인 설명

 

Try, Catch 구문을 사용하는 이유는 코드를 작성할때, 모든 경우의 알고리즘을 작성할 것이 아니기 때문입니다.

또한 에러레벨보다 다양하게 예외의 상황에 대처하게끔 만들수있기 떄문이죠.

Try , Catch 구문을 통해 정교한 프로그램을 만드시기 바랍니다!

 

try
{
    obj := ComObjCreate("ScriptControl")
    obj.ExecuteStatement("MsgBox ""내장 VBScript""")
    obj.InvalidMethod() ; 런타임 에러를 발생시킴
}
catch e
{
    ; For more detail about the object that e contains, see Catch.
    MsgBox, 16,, % "Exception이 throw되었습니다!`n`nwhat: " e.what "`nfile: " e.file
        . "`nline: " e.line "`nmessage: " e.message "`nextra: " e.extra
}

 

Catch 에러레벨.ahk

  1. ㅁㄴㅇㄹ 2015.03.14 19:07 신고

    사이트 한번 깔끔하네요 와 진짜 잘만들었네ㅋㅋㅋ 강의도 깔끔하고

 

FileInstall 명령어를 아시는지요?

 

파일인스톨, 스크립트 실행시 파일을 설치하는 명령어입니다.


파일은 어떠한 것이든지 상관없습니다. Dll 파일이든지, 이미지파일이든지 혹은 EXE 실행파일이라 하더라도요.


FileInstall, 설치할파일명.확장자, 설치할경로\파일명.확장자, 1


간혹, FileCopy 명령어와 혼동하시는분이 있는데,

FileCopy 파일카피는 파일을 복사해서 이동하는것이기 때문에

복사할 파일이 반드시 있어야 합니다.


파일인스톨은 파일을 '설치'합니다. 컴파일했을 경우에는 설치파일이 EXE파일안에 내장됩니다.

즉, 컴파일후에는 설치할 파일이 없어도 파일을 설치경로에 설치합니다.


따라서, 다른 파일을 이용해야한다거나 이미지파일을 포함해서 프로그램을 배포하고싶을때 유용하겠죠?


FileInstall 명령어 맨 뒤에 붙은 , 1 은 덮어씌운다는 표현입니다.

생략한다면 설치경로에 파일이 이미 존재할시에는 설치를 진행하지 않습니다.


FileInstall, Name.txt, C:\Name.txt, 1        ; 소스경로에 있는 Name.txt 파일을 C드라이브-Name.txt에 설치
FileInstall, Skin.dll, %A_Temp%\Skin.dll, 1    ; 소스경로에 있는 Skin.dll파일을 Temp폴더-Skin.dll로 설치


파일인스톨은 단점을 굳이 적는다면

디렉토리(경로)에 파일을 설치하는것이기때문에

많은 파일들을 설치할 경우에는 설치된 파일들때문에 더러워보이기도합니다.


이를 해결할 방법으로는

FileSetAttrib 명령어를 사용해서 파일의 속성을 숨김으로 설정해 보이지않게 설정하는 방법이 있지만

만약 사용자의 컴퓨터가 '숨김 파일 보기' 를 설정했다면 이마저도 소용이없습니다.


다음에는 파일자체나, 경로를 가상화함으로서 파일인스톨 역시 가상화시키는 글을 적어보겠습니다.

가상화방법을 사용하면 파일을 가상의 경로 즉, 램메모리에 설치함으로서

실제로는 어떠한 위치에서도 파일을 찾아볼수없게 만들수있습니다.


다음 글을 기대해주세요!

  1. resuobohc 2018.02.01 10:18 신고

    소스경로라는것이 보통 어디를 말씀하시는건가요 ? 스크립트가 있는 경로인가요 아니면 오토핫키가 설치되어있는 파일인가요 ?

 

명령어집합을 하나의 코드로 만들어야 할 때가 있습니다.

사실 goto나 gosub 를 사용하지않아도 되는데, 소스를 깔끔하고 헷갈리지 않게 작성하려면 필요합니다.

 

Goto, 라벨명

Gosub, 라벨명

 

라벨명:

명령어

return

라벨명 ex) Goto, MyLabel

영숫자혼용의 라벨명을 설정하시면 됩니다.

 

예제 #1                                                                              

Goto, MsgShow

MsgBox, gosub만 보일 메시지박스

return

 

MsgShow:

MsgBox, 메시지박스

return

 

위 소스를 실행해보면 '메시지박스' 만 나타나고 이후엔 아무런 반응이 없습니다.

하지만 Gosub를 사용한다면, '메시지박스'가 나타난후 'gosub만 보일 메시지박스' 까지 나타납니다.

차이점을 아시겠죠?

 

Goto는 지정한 라벨로가 Return 전까지 모든 명령을 실행하고

Gosub 는 지정한 라벨명령어 모두 실행하고 원래 위치로 돌아와 gosub 다음 명령어까지 실행합니다.

 

숫자를 저장하고, 대입하고, 문자열을 저장해놓거나, 그리고 그것끼리 비교할떄

변수(Variable)가 필요합니다.

 

변수를 사용하는 방식은 트레디셔널(Traditional) 과 익스프레션(Expression)이 있습니다.

트레디셔널은 아래처럼 표현하고 사용합니다.

 

변수명 = 숫자

변수에 숫자를 대입합니다. ex) Var1 = 123

 

변수명 = 문자열

변수에 문자를 대입합니다. ex) Var2 = Hello

 

새로운변수명 = %대입할변수명%

새로운 변수에 변수를 대입합니다. ex) Var3 = %Var1%

 

변수를 초기화 할때는, 변수명 =

그리고 문자열변수는 변수명 := ""    이렇게 합니다.

 

변수를 메시지박스로 출력할때는, MsgBox, %Var1% 이런식으로 사용합니다.

 

익스프레션은 아래처럼 사용합니다.

 

변수명 := 숫자

ex) Var1 := 123

 

변수명 := "문자열"

변수에 문자를 대입합니다. ex) Var2 := "Hello"

 

새로운변수명 := 대입할변수명

ex) Var3 := Var1

 

출력은 트레디셔널과 다르게, % 를 하나만, 그리고 띄어쓰기를 합니다. 아래처럼요

MsgBox, % Var3

 

조건문 If 와 연계해 사용해보면

A := "안녕하세요"
if A = 안녕하세요
    MsgBox A는 안녕하세요를 담고있어요
return

 

숫자변수끼리 서로 더할때는

A := 10

B: 5

C := A + B

 

곱하기는 * 로 표현합니다.

2*1 = 2

나누기는 / 로

2 / 2 = 1

 

현재 숫자변수에 +1 해주는것은

A ++

-1 하는것은

A --

 

  1. 으아앙앙 2015.08.20 12:51 신고

    트레디셔널과 익스프레션 변수선언 방식간의 명백한 차이가 있나요??

    • BlogIcon 예지력 2015.08.20 14:22 신고

      네, 나중에 함수관련해서 변수사용할때는 익스프레션을 사용해요

  2. ㄴㄹㄴㅇ 2015.08.28 05:51 신고

    또 도움받고갑니당 ㅋㅋ

  3. 뽀이팅 2015.12.30 10:17 신고

    Msgbox, % Var1 입니다.
    이것처럼 익스프레션방식으로 변수안에 있는 내용을 Msgbox로 문자열과 같이 출력이 가능한가요?

 

이럴때 이렇게 하고, 저럴땐 저렇게 하고..

여러가지 케이스를 나누어서 모든 경우의 수를 제어하는 알고리즘에 필요한 명령어입니다.

 

경우가 여러가지인 만큼 If 의 종류는 너무나도 많습니다.

이유는 수치를 비교해야할떄도 있고, 파일의 유무를 판단해야 할 수도 있고

그럴때마다 If 를 사용하기 때문이죠

 

하지만 가장 기본형인 If 만 알면 나머지는 따로 배울 필요도 없습니다. 다 파생된거라서요.

 

If 조건문

 

●조건문

If 조건문 은 조건문이 참일때 다음 명령어실행을 기본으로 합니다.

예제를 다 확인해보세요.

 

예제 #1 ( ; 이후에 오는 말은 주석입니다. 코멘트 같은거에요 소스실행할떄와 컴파일할때 무시합니다)

A := 5 ;A라는 변수에 5라는 숫자를 대입

If A = 5

MsgBox, A가 5가 맞네요

return

 

위처럼 한번만 비교할떄는 저렇게 끝나지만,

만약 A가 5가 아닐경우에 메시지박스를 띄우려면

 

If A <> 5 ; <> 은 아닐경우에 라는 뜻입니다

를 사용할수도 있고

 

If A = 5

MsgBox, A가 5가 맞네요

else

MsgBox, A는 5가 아닙니다

return

 

이렇게 할 수도 있습니다. 하지만 else는 예/아니오 처럼 단순한 이분법적인 분류만 가능해요

자세하게 케이스분류 하는방법은 아래 예제를 확인해보세요.

 

예제 #2                                                                                                                         

A := 10

B := 5

If A > B ;A가 B보다 크다면

MsgBox, A가 B보다 크네요

If A < B

MsgBox, B가 A보다 크네요

else

MsgBox, A와 B는 같습니다.

return

 

if A = 5 ; A가 5일때

if A <> 5 ; A가 5가 아닐떄

if A < 5 ; A가 5보다 작을떄

if A > 5 ; A가 5보다 클때

if A between 작은숫자 and 큰숫자 ; A가 작은숫자와 큰숫자 사이일때

  1. BreezeRabbit 2015.03.17 10:04 신고

    if (1 > a > 5 and 6 > b > 9)
    ..............
    a는 2~4까지이고 b는 7~8만 되었을때 실행해라

    이런 문은 안되는건가요?

    • BlogIcon 예지력 2015.03.17 13:11 신고

      if 문을 두번 사용하셔야해요
      또는 아래처럼 a + b 의 합을 이용해서 구할수도있겠죠?

      c := a + b
      if (c bettwen 9 and 12)

    • BreezeRabbit 2015.03.17 14:13 신고

      헛! 그런방법이 +_+ 우왕+_+
      감사합니다 ㅋ

  2. BlogIcon da 2015.07.20 23:20 신고

    예제 2번을 그대로 복사해서 만들어봤는데 A가 무슨값이든 항상 B가 더 크다고 나오네요 왜그럴까요??

    • BlogIcon 예지력 2015.07.21 13:35 신고

      코드에 오류는 없습니다.
      A = 1
      B = 2 으로 :뺀 변수로 확인해보세요.

  3. 짱짱맨 2015.09.15 14:56 신고

    else만 썼었는데 여기서 새로배우고가네요 감사합니다

지난 시간에는 Loop, While 등의 반복문을 배웠습니다.

횟수설정을 하지않은 무한반복문은 어떻게 종료해야할까요? 그것이 오늘 배울 Break와 Continue 명령어입니다.

 

Break

 

예제 #1                                                                                            

Loop

{

MsgBox, 안녕하세요

sleep, 1000

MsgBox, 이제 루프문을 나갈꺼에요

Break

}

MsgBox, 루프문 나왔네요!

return

 

 

Continue, 루프라벨

*Break 와의 차이점 - Break는 그냥 반복문 탈출이지만, Continue는 나가는 위치를 지정해줄수있음

 

●루프라벨

생략가능, 생략시 Break와 동일한 기능을 합니다.

 

예제 #2                                                                                            

Loop

{

if A_index > 5

Continue

MsgBox, %A_index% 회 반복중입니다! 총 5번뜰꺼에요.

}

return

 

A_index 가 루프문이 돌고있는 횟수라는거는 아시죠?

if A_index > 5 는 루프문이 5번 이상으로 반복했을 경우에 ` 라는 뜻이 되겠죠 (이후의 조건문 강의 확인)

5번 반복했을때, Continue 를 사용해서 루프문 괄호밖으로 나가면서 끝나겠죠

 

예제 #3                                                                                            

Loop {
 MsgBox %A_Index% 번째.. 5번때 나감
 if A_Index = 5
  continue, Hello
}

 

Hello:
Loop, 1
{
 MsgBox, 루프문 나왔네요
}
return

 

Continue, Hello 로 인해서

Hello 라벨로 이동합니다. 라벨의 명령어를 입력할땐 반드시

라벨이름:

명령어

return

 

이렇게 적어야합니다.

  1. 궁금 2015.09.04 02:25 신고

    혹시 지금도 티스토리 관리하시는지는 모르겠지만.. 궁금한게 생겨서 질문합니다.
    예를 들어
    loop {
    if {
    continue
    }
    }
    요런 구문이 있다고 할 때, continue가 if 밖으로 나가는건지, 아니면 loop 밖으로 나가게 하는건지 궁금합니다.

키보드입력과 마우스클릭 그리고 Sleep 까지 배웠다면 기본적인 자동프로그램을 제작하실 수 있습니다.

하지만 아직 Loop, While 같은 반복명령어를 모르신다면

 

Send, A

Sleep, 1000

Send, A

Sleep, 1000

 

이런식으로 적고 계실껍니다. 하지만 Loop를 이용하면

Loop, 2

{

Send, A

Sleep, 1000

}

이렇게 하시면되죠

오늘 배울 명령어는 반복문이라 부르는 매우 편리한 것입니다.

 

Loop, 횟수

 

횟수 예) Loop, 5

생략시 무한반복합니다, 무한반복을 종료하기 위해서는 루프를 탈출시키는 명령어 break를 사용해야합니다.

*A_Index 는 루프가 몇번쨰 실행중인지를 나타내는 변수입니다. (변수에 대해서는 다음강좌에서 배웁니다)

 

예제를 확인해볼까요?

루프문으로 사용할 명령어들을 {, } 괄호를 이용해 감싸줘어야합니다.

Loop, 7
{
    MsgBox, %A_Index%
    Sleep, 100
}

return

 

실행시키면 1부터 시작하는 메시지박스가 7까지 나타날껍니다.

 

반복문 심화편

Loop는 이 외에도 다른 기능이 많고, 비슷한 기능을 하는 While, Loop~ Until 등이 있지만

여러분이 아직 조건문을 배우지 않았더라면, 사용이 어렵습니다.

밑에 설명하는 반복문은 다음강의인 조건문, 변수강의를 읽고 다시 읽어보세요.

 

While 조건문

 

조건문 예) While x < y

모든 조건문이면 서술이 가능합니다. 하지만 반드시 익스프레션으로 서술해야합니다.

While은 이 조건문이 참(사실)일 경우에만 반복을 합니다. 거짓으로 판명될때는 반복을 중지합니다.

 

예제

A := 10

B := 1

While A > B

{

MsgBox, 아직 A가 B보다 큽니다.

B ++

}

 

실행해보면, B가 A보다 커지기전가지는 계속해서 메시지박스가 뜨는걸 확인하실수있습니다.

 

Loop {

명령어들

} Until 조건문

 

●조건문

조건문이 참이 될 경우에만 반복을 종료합니다. 거짓일 경우에는 한번 더 반복합니다. 조건문은 반드시 익스프레션이어야합니다.

 

예제

A  := 10

B := 1

Loop {

MsgBox, 아직 A가 B보다 큽니다.

B ++

} Until B > A

  1. 질문자 2015.10.20 09:06 신고

    익스프레션이 무슨뜻이에요?

  2. resuobohc 2018.02.01 08:52 신고

    B++는 무슨 의미를 가지나요 ? 메시지창이떠도 계속해서 확인을 눌러도 B가 커지는것같지는않은데...

업무자동화나, 게임매크로 등에 마우스클릭이 빠질수가 없겠죠?

키보드를 컨트롤 하는것은 지난시간에 배웠으니, 이제 마우스를 제어해보겠습니다.

 

MouseClick, 버튼, x좌표, y좌표, 클릭횟수, 속도, 옵

버튼 예) MouseClick, L

클릭할 버튼을 지정합니다. 기본은 왼쪽버튼으로 지정되어 있습니다 따라서 생략시 왼쪽버튼을 클릭합니다.

오른쪽버튼은 'Right' 혹은 'R', 마우스 휠버튼은 'Middle" 혹은 'M' 으로 적으면 됩니다.

X좌표, Y좌표 예) MouseClick, L, 10, 20

클릭할 좌표를 지정합니다. 생략시 현재의 마우스위치를 클릭합니다. (마우스좌표는 전체화면상의 좌표와 활성창의 좌표로 나뉩니다)

스크립트 맨위에 'CoordMode, Relative' 로 설정하지않는이상, 명령어는 CoordMode, Screen 으로 전체화면상의 위치라고 판단합니다.

클릭횟수 예) MouseClick, L, 10, 20, 2

클릭할 횟수를 적으시면 됩니다. 생략시 1번만 클릭합니다.

속도 예) MouseClick, L, 10, 20, 2, 0

마우스가 움직이는 속도를 0과 100사이로 지정합니다. 0은 마우스를 순간이동의 속도로 움직이고 100으로 갈수록 천천히 움직입니다.

생략시, 기본 마우스속도대로 움직입니다.

옵션 예) MouseClick, L, 10, 20, 2, 0, D

생략시, 클릭을 실행합니다 (마우스 눌렀다가 떼는). 'D' 로 적을시, 버튼을 누르고있고 'U'는 버튼을 뗍니다

'R' 로 적을시 마우스클릭 명령어는 X좌표 Y좌표를 클릭하지않고, 현재의 마우스위치에서 X축으로 X좌표 만큼, Y축으로 Y좌표만큼 이동합니다.

 

이렇게보니 마우스클릭이 참 복잡해보이네요.

사실 굉장히 쉽습니다. 안쓰는 옵션은 그냥 생략하면 되거든요. 이렇게요

 

MouseClick,, 10, 20

 

10,20 좌표를 마우스왼쪽클릭 1번하는겁니다.

 

더 귀찬다!?

Click, 10, 20

  1. yelm 2015.03.10 16:11 신고

    질문해도 될런지요...마우스 클릭을 비활성 창에서도 되게 하려면 어떻게 해야되나요 ?

    • BlogIcon 예지력 2015.03.10 18:46 신고

      ControlClick 명령어를 사용하셔야합니다.

      비활성이 관한 부분은 나중에 시간이 날때 따로 정리할생각인데 영 여유가없군요 ㅠㅠ

  2. 안녕하세요. 2015.09.02 15:42 신고

    작동은 되는데 게임창안에서만 작동을 안해요.. 왜 일까요?
    어느 부분을 공부하면 작동 되도록 할 수 있을까요?

    • BlogIcon 예지력 2015.09.03 04:43 신고

      스크립트 상태로 실행하지 말고, 컴파일후 관리자권한으로 실행해보세요.
      또 컴파일된 파일을 패킹해보시눈것도 하나의 방법입니다.
      그 외에는 게임보안프로그램이 오토핫키의 가상입력을 차단하는거니 우회하는 작업이 필요해요. 근데 그 부분은 프로그램마다 달라서 힘듭니다.
      제가 하는 게임이 없어서 더 드릴만한 팁이 없군요

  3. Aiden 2015.12.28 11:45 신고

    내용이 좋은대, 출처남기고 퍼가도 될까요?? Tistory라서 스크랩 기능이없네요.

 

 

Sleep 명령어는 사용자가 지정한 시간만큼 기다리고 다음 명령어를 진행합니다.

 

Sleep, 밀리세컨드

 

1초 = 밀리세컨드 1000

ex) Sleep, 3000 = 3초 동안 슬립

 

예제 #1                                                              

F1::

MsgBox, 시작

Send, {A}

Sleep, 2000

Send, {B}

return

 

F1키를 누를시 시작 메시지박스가 출력되고,

A 키를 누른뒤 2초뒤에 B키를 누릅니다.

 

다시 말씀드리면 Send, A 는 Send, {A} 와 다릅니다

전자는 A를 입력하고 (문자 A)

후자는 키보드 A키 (한글 ㅁ)키를 누릅니다.

  1. 핫키 2016.03.09 00:57 신고

    실행을 해보면 A는 영어로 나오고 B는 한글 ㅠ로 나오는데 왜그런건가요?

핫키 HotKey

오토핫키라는 프로그램 이름에서 알수있듯이

오토핫키는 단축키 지정에 굉장히 특화되어 있습니다. 타 언어보다 훨씬 직관적으로요

(핫키,핫스트링 지정하는걸 Key Bind 라고 부릅니다. 헬프파일이나 외국포럼볼때 참고하세요)

 

아래 예제를 확인해볼까요! 왼쪽은 기본형, 오른쪽이 예제입니다.

 

핫키::                     F1::

명령어                    MsgBox, Hello

명령어                    MsgBox, World

return                    return

 

핫키를 선언하기위해서는 키 뒤로 :: 마크를 해줘야합니다.

위 예에서는 F1:: 으로 되어있으니, 'F1' 키가 핫키라는걸 명시해주는거겠죠

그 뒤에서부터 return 사이에 핫키를 눌렀을 때 실행하는 명령어들을 적으시면 됩니다.

return 은 해당 루틴을 종료한다는 말로 알아두시면 됩니다.

return을 적지않으시면 밑에 명령어까지 다 실행해요.

 

위 명령어를 실행해볼까요?

 

 

 

 

 

 

위 예는 핫키가 실행할 명령어가 두줄 (메시지박스 2개)이기 때문에

 

F1::

MsgBox, Hello

MsgBox, World

return

 

이런식으로 적었지만, 명렁어가 하나일 경우에는 아래와 같이 핫키선언후에 바로 이어주면 됩니다. return 도 필요없구요

 

F1:: MsgBox, Hello!

 

 

핫스트링 Hotstrings

핫키와 함께 핫스트링이라는 것이 있습니다. 

이는 핫키처럼 특정키가 아니라 문자열 등을 단축키로 설정할수 있고

줄임말을 입력할시 자동으로 긴문장으로 변환시킬수도있습니다.

 

예로 'ㅎㅇ' 라고 치면 자동으로 '안녕하세요' 로 바꿀수있습니다.

아래의 예를 확인하죠

문자열입력후, 스페이스바나 엔터같은 키를 눌러줘야 작동합니다

 

::문자열::바뀔문자열                    ::gd::안녕하세요

 

핫키도 그렇고 핫스트링 선언할때는 영어문자로 지정해야합니다.

'ㅎㅇ' 지만 키보드에서 ㅎ과 ㅇ의 위치 g와 d를 사용하세요.

 

ㅎㅇ누르고바로 바뀌시길 원하시면

 

:*:ㅎㅇ::안녕하세요

타이핑되면 바로바뀌게됩니다

그런데

ㅎㅇㅎㅇ

이렇게 치면 안바뀌죠 붙어있어서그렇습니다

그럴땐

:*?:ㅎㅇ::안녕하세요

:*!:ㅎㅇ::안녕하세요
이젠 붙어있어도 바로바로 바껴요

  1. 나그네 2015.04.27 14:31 신고

    오.... 깔끔한 설명 좋네요 ㅎㅎ

    근데 저는 scite4로 오토핫키를 짜는데 왜 저는 아무리해도 핫스트링이 안 먹힐까요..

    ::abc:: asdf1234@naver.com를 먼저 입력하고 다음 줄에서 abc를 누르고 스페이스나 엔터를 쳐도 반응이 없네요 -_-

    scite 최신버전으로 설치했는데(포터블X) 아....그것 때문에 답답해서 검색하다가 여기까지 오게됐는데 혹시 이유 아실까요?

    세팅값에서 뭘 만져줘야 하는 건지 ㄷㄷㄷ

    • BlogIcon 예지력 2015.04.28 11:42 신고

      F5(오토핫키 실행) 하신거 맞으시죠? 핫스트링도 오토핫키 파일 실행하셔야해요.
      Scite문제일 경우는 없을텐데, 핫키는 작동하는지 확인해보세요

  2. 나그네 2015.04.30 12:55 신고

    아오 ㅋㅋㅋ
    저장하고 실행해야된다는 걸 모르고 한참을 헤맸군요 -_-a
    지금 해보니 잘 되네요 그것도 모르고 며칠을 삽질했던 ㅜㅜ
    완전 답답했었는데 정말 고맙습니다~

  3. 갓예지력 2015.07.25 11:48 신고

    키가 눌린 상태로 스크립트 reload시에 눌린상태가 지속되기 때문에 다시 그 키를 눌러줘야 하는 번거러움이 있는데요.
    스크립트 실행을 그만둘시에 눌려있던키가 다 풀리게 할 수 있는 명령어나 방법이 없을까요?
    핫키가 한 스크립트내에서 중복지정이 안되기 때문에 한개를 더켜서 키 업버튼으로 대체하고 있습니다.
    !요약:crtl키가 눌린 상태로 reload시에 다시 crtl을 직접 누르거나 스크립트 하나를 더켜서 crtl up을 해주지 않는 이상 아무것도 할 수 없는데 방법이 없을까요?

    감사합니다

    • BlogIcon 예지력 2015.07.25 13:23 신고

      스크립트 최상단에
      Suspend, Off
      OnExit, Exit 추가하시고
      reload 부분을 아래처럼 수정해보세요,

      Exit:
      Suspend, On
      Reload

  4. 짱짱맨 2015.09.15 14:45 신고

    신기하네요 ㅋㅋ

오토핫키의 Send 명령어는 키보드나 마우스 입력을 보냅니다.

Send 의 종류도 SendInput, SendPlay, 등으로 많습니다.

 

구조는 똑같지만 속도(사실 사람이 체감하진못하지만) 와 호환성 그리고 특수키 지원 등의 미세한 차이가 있습니다.

현재의 AutoHotkey L 버전 기준으로는 SendInput과 SendPlay 를 가장 많이 사용하는것 같네요.

 

이유는 기본 Send 와는 달리, 키입력중에 들어오는 다른 입력을 지연시키기 떄문입니다.

따라서, 실수로 키보드나 마우스를 클릭해도 명령어 수행에 아무런 영향을 받지 않습니다.

 

문자 입력 ㅡ Send, 문자

Send, A

→ A 문자 입력

 

키 입력 ㅡ Send, {키}

Send, {Enter}

→ 엔터키 입력

 

마우스 클릭 ㅡ SendInput, {Click, 횟수, x좌표, y좌표) , Click(X좌표, Y좌표)

SendInput, (Click, 3, 100, 200)

→ 화면 x좌표 100, y좌표 200 지점을 마우스 왼쪽 3번 클릭

SendInput, (Click, 2)

→ 현재 마우스 위치에서 왼쪽버튼 2번 클릭

Click

→ 현재 마우스커서가 있는곳, 마우스 왼쪽 클릭

C언어는 Printf("Hello World") 로,

오토잇은 MsgBox(0,'','Hello World') 로

 

그럼 오토핫키는 어떻게 할까요

아마 제 생각에는 오토핫키가 가장 간결하게 소스를 작성할 수 있는 언어에요

아래에서 소스를 확인해보죠

 

MsgBox, Hello World

 

C언어나 오토잇처럼 따옴표('',"") 처리 할 필요도 없고,

괄호처리를 할 필요도 없습니다.

 

위 소스대로 실행시키면 아래와 같은 메시지박스가 출력됩니다.

 

 

사실 단순히 Hello World 만 출력하기엔 아래 소스도 충분합니다.

 

MsgBox, 내용

 

하지만, 메시지박스의 아이콘표시나, 시간, Yes or No 와 같은거를 표현하기 위해 메시지박스의 옵션에 대해 알아보죠

 

메시지박스의 구조는

 

MsgBox , 옵션, 제목, 내용, 시간

 

으로 옵션이나 제목 등을 스킵하려면 , 로 넘겨주시면 됩니다.

예를 들어, MsgBox,,,Hello World 는 MsgBox, Hello World 하고 동일한 기능을 합니다.

 

옵션

메시지박스의 타입과, 버튼설정을 담당합니다. 기본값은 0 으로 위 사진과 같은 '확인' 버튼만 있습니다.

 

제목

메시지박스의 제목입니다. 생략할경우 스크립트파일명이 제목으로 표시됩니다.

 

내용

내용입니다. 줄바꿈은 `n 을 적고 이후에 글을 적으면됩니다. 예)Hello `n안녕하세요

 

시간

추가기능으로, 사용하지않을 경우에는 내용 적고 , 쓸필요없이 끝내면 됩니다.

메시지박스를 표시할 초를 입력합니다.

 

 

저렇게 일일이 적을수도 있지만,

오토핫키 에디터인 'Scite4AutoHotkey' 는 메시지박스 생성툴이 존재합니다.

 

 

 

버튼과, 아이콘, 시간 등을 설정할 수 있습니다.

 

 

'Test' 버튼을 클릭해 메시지박스가 옵션설정대로 잘 실행되는지 확인해볼 수 있구요.

 

완성된 메시지박스는 'Insert in SciTE' 를 클릭해 소스코드안에 포함할 수 있습니다!

  1. 혜나토리 2015.11.27 23:36 신고

    좋은설명감사합니다 ^^

  2. Rosantex 2017.04.14 09:08 신고

    이런게 있었다니! 좋은 정보 정말 감사드려요.

  3. 왕초보 2017.05.24 09:43 신고

    메세지를 편하게 설정할수 있는 방법이 있었네요..
    많은 초보분들이 알지 못하는 좋은 강좌 감사합니다...

+ Recent posts