본문 바로가기

Windows/_Windows Programming

타이머(Timer)

타이머 :

한 번 지정해 놓기만 하면 일정한 시간 간격을 두고 연속적으로 게속 발생한다. 주기적으로 같은 동작을 반복해야 한다거나 여러 번 나누어 해야 할 일이 있을 때 이 메시지를 이용한다.

타이머 메시지를 사용한 예제이다.
(윈도우 프로그램 기본틀에 수정을 하면 된다 http://dakuo.tistory.com/entry/1)

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage,
                                              WPARAM wParam, LPARAM lParam)
{
      HDC hdc;
      PAINTSTRUCT ps;
      static HANDLE hTimer;
      SYSTEMTIME st;
      static char sTime[128];

      switch(iMessage)
      {
             case WM_CREATE:
                     hTimer = (HANDLE)SetTimer(hWnd, 1, 1000, NULL);
                     return 0;
             case WM_TIMER:
                     GetLocalTime(&st);
                     wsprintf(sTime, "지금 시간은 %d:%d:%d 입니다",
                                               st.wHour, st.wMinute, st.wSecond);
                     InvalidateRect(hWnd, NULL, TRUE);
                     return 0;
             case WM_PAINT:
                     hdc = BeginPaint(hWnd, &ps);
                     TextOut(hdc, 100, 100, sTime, strlen(sTime));
                     EndPaint(hWnd, &ps);
                     return 0;
             case WM_DESTROY:
                     KillTimer(hwnd, 1);
                     PostQuitMessage(0);
                     return 0;
        }
        return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}

(참고 : 현재 시간을 구할 때는 GetLocalTime 함수에 SYSTEMTIME 구조체를 넘겨준다)

WndProc 선두에

static HANDLE hTimer;              // 타이머 핸들 hTimer 선언
SYSTEMTIME st;                      // SYSTEMTIME 구조체 st 선언 
static char sTime[128];             // 시간값을 문자열로 변경하여 저장할 sTime 문자열 선언

WndProc 에서 첫 번째로 처리하는 메시지는 WM_CREATE 메시지다. 윈도우가 처음 생성될 때 발생하는데 이 메시지에서 프로그램 시작시 꼭 한 번만 초기화되어야 할 처리를 해 준다.
(예 : 프로그램 실행에 필요한 메모리 할당, 전역변수에 초기값을 대입하는 등의 초기화처리)
이 예제에서 WM_CREATE 메시지에서 SetTimer 함수를 사용하여 타이머를 생성시켰다.

UINT SetTimer(HWND hWnd, UINT nlDEvent UINT uElapse, TIMERPROC lpTimerFunc)

hWnd 인수는 타이머 메시지를 받을 윈도우인데 통상 WndProc의 파라미터로 전달되는 hWnd를 써준다.

두 번째 파라미터 nlDEvent는 타이머의 번호를 지정한다. 하나의 타이머만 사용할 경우 1을
여러 개의 타이머를 사용할 경우 nlDEvent 에 겹치지 않도록 번호를 부여한다.
(예 : 세 개의 타이머를 사용하면 1, 2, 3 의 타이머 번호를 부여)타이머 번호는 WM_TIMER
메시지에서 타이머를 구분하기 위한 표식으로 사용한다.
(참고 : 두 개의 타이머를 사용할 경우에도 발생하는 메시지는 WM_TIMER 로 동일하다.
다만 wParam 으로 전달되는 타이머의 ID 를 통해 이 메시지가 어느 타이머에서 발생했는지를 체크하여 두개의 타이머를 효율적으로 조절할수 있다)

세 번째 파라미터 uElapse 는 1/1000 초 단위(ms, 기본단위)로 타이머의 주기를 설정한다.
(참고 : 타이머의 최대 해상도는 Windows NT/2000 에서 0.01초에 불과하기 때문에 주기를 아무리 짧게 해도 타이머 메시지는 1초에 100회 이상 발생하지 않는다 또 ms가 기본단위인 이유는 cpu clock time이 ms단위이기 때문이다)

네 번째 파라미터는 타이머 메시지가 발생할 때마다 호출할 함수를 지정한다. 사용하지 않을 경우 NULL로 설정한다.
(참고 : SetTimer의 리턴값은 첫 번째 파라미터가 NULL 일 경우에 한해 특별하게 사용하는 것이며 거의 사용되지 않는다)

이제 ID 1번으로 1초 간격으로 WndProc에 WM_TIMER 메시지가 전달된다.
(참고 : WM_TIMER 메시지는 wParam 으로 타이머 ID를 전달하며 lParam 으로 타이머
메시지 발생시 호출될 함수의 번지가 전달된다)

case WM_TIMER:
         GetLocalTime(&st);
         wsprintf(sTime, "지금 시간은 %d:%d:%d 입니다",
                                               st.wHour, st.wMinute, st.wSecond);
         InvalidateRect(hWnd, NULL, TRUE);
         return 0;

WM_TIMER 메시지가 발생하면 GetLocalTime 함수로 시간을 조사한 후 출력을 위해 wsprintf로 sTime 문자열로 변환해 둔다. 또 시간이 WM_TIMER 메시지가 올 때마다 값을
갱신시키기 위해 InvaildateRect 함수를 호출한다.

WM_PAINT 에서는 sTime 문자열을 TextOut 으로 출력한다.

BOOL KillTimer(HWND hWnd, UINT ulDEvent);

타이머는 시스템 전역 자원이므로 필요가 없어지면 제거해 주는 것이 좋다. KillTimer 의
첫 번째 파라미터로 이 타이머를 소유한 윈도우 핸들을 넘겨 주며 두 번째 파라미터로 타이머 ID를 넘겨 준다.

이렇게 하면 여러 문제가 생긴다.
첫 번째 문제는 프로그램이 실행 된 후 1초 뒤에 시간이 보인다는 것이다.
SetTimer에서 1초 간격으로 WM_TIMER 메시지를 발생시키므로 프로그램 실행 후 1초 뒤에 시간이 보이는 것이다. 따라서 프로그램이 실행 직후에 강제로 WM_TIMER 메시지를 발생시켜야 한다.

LRESULT SendMessage(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

(참고 : 메시지는 사용자의 동작에 의해서나 시스템의 상황 변화에 따라 발생하는 것이 원칙이지만 강제로 메시지가 발생한 것처럼 만들어야 할 때도 있다. 이때 SendMessage를 사용하여 hWnd 윈도우로 Msg 메시지를 보낸다
메시지 기반의 운영체제인 윈도우즈에서 SendMessage 는 빈번히 사용되는 중요한 함수다.
어떤 종류의 메시지도 누구에게나 보낼 수 있으며 차일드 컨트롤을 프로그래밍하는 중요한 수단이다)

WM_CREATE 메시지 처리에 SendMessage(hWnd, WM_TIMER, 1, 0); 을 추가한다.

두 번째 문제는 비효율적이라는 것이다.
단 한줄의 텍스트를 갱신하기 위해서 우리는 지금 화면 전체를 갱신하고 있다.

InvalidateRect(hWnd, NULL, TRUE);

hWnd 윈도우를 무효화 시키되 두 번째 파라미터가 NULL 이므로 화면 전체가 무효화 되며
세 번째 파라미터가 TRUE 이므로 화면을 지운후 다시 그리게 된다.

따라서 무효화 영역을 최소화 하여 꼭 필요한 부분만 무효화하도록 해야 한다.

static RECT rt = {100, 100, 400, 120}; 사각형을 만든 뒤
InvalidateRect(hWnd, &rt,TRUE); 에 NULL 대신 만든 사각형 rt 을 넣어준다.

이제 rt 영역 외에는 지우지도 그리지도 않게 되므로 프로그램의 속도가 향상된다.
이런 처리들을 깔끔하게 해줄수록 프로그램의 질이 향상된다.


콜백함수(Callback Function) :

일반적으로 API 함수들은 운영체제가 제공하며 프로그램에서는 이 함수들을 호출해서 운영체제의 서비스를 받는다. 하지만 콜백함수는 응용 프로그램이 제공하며 운영체제가 필요할 때 호출하는 함수이다. 호출되는 방향이 거꾸로 되었기 때문에 콜백이라 부른다.
즉, 운영체제에 의해 호출되는 프로그램 내부의 함수이다.


콜백함수는 조건이 될 때 시스템이 불러주는 함수이므로 그 원형이 미리 정해져 있다. 운영체제가 어떤 특정한 때에 특정한 파라미터와 함께 호출하기로 약속이 되어 있으므로 원형은 반드시 지켜야 한다.
(참고 : 이름은 마음대로 붙여도 상관없다)


만약 프로그램이 실행되는 동안 지속적으로 수행해야 할 작업이 있다면 DOS의 경우에는

while(1)
{
      작업;
}

위와 같이 코드를 작성할 것이다.

하지만 윈도우즈와 같은 멀티 태스킹에서는 이런 방식으로 해서는 안된다. 한 프로그램이 제어권을 독점하여 다른 프로그램에 실행시간을 주지 못하기 때문이다.

또한 같은 프로그램으로 전달 되는 다른 메시지도 처리해야 하는데 무한 루프에서 CPU를
독점하면 다른 작업은 전혀 할수가 없다. 따라서 메시지가 전달 되었을 때에 한해 필요한 작업을 하도록 해야 한다. 이때 타이머 메시지를 사용하여 구현할 수 있다.

SetTimer 의 네 번째 파라미터는 TIMERPROC lpTimerFunc 인데 타이머 프로시저 함수의 포인터를 가리킨다. 매 시간마다 이 포인터가 가리키는 함수가 대신 호출된다.
즉, 지정한 시간 간격으로 WM_TIMER 메시지 대신 네 번째 파라미터의 함수가 호출된다.
(참고 : NULL 일 경우 첫 번째 파라미터로 넣어준 hWnd로 WM_TIMER 메시지가 전달된다)
타이머 함수는 다음과 같은 원형으로 작성된다.

VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime);

4개의 파라미터를 가지는데 hwnd는 타이머를 소유한 윈도우의 핸들, uMsg 는 WM_TIMER, idEvent 는 타이머 ID, dwTime 은 윈도우즈가 실행된 후의 경과시간이다.
(참고 : 콜백함수를 사용하여 이 문제를 해결할 수 있지만 WM_TIEMR 메시지를 받아 처리를 할 수도 있다(네 번째 파라미터에 NULL) 하지만 WM_TIMER 메시지는 다른 메시지가 있을 경우 실행 순서에 밀려 늦게 호출되는 경우가 있지만 콜백함수는 정확한 시간에 호출된다. 따라서 정확도를 위해 콜백함수를 사용한다)

'Windows > _Windows Programming' 카테고리의 다른 글

윈도우 프로그램 기본 틀  (0) 2009.12.23
윈도우 관리 메시지  (0) 2009.12.23
입력하기  (0) 2009.12.01
출력하기  (2) 2009.11.28
윈도우 기초 프로그래밍  (0) 2009.11.27