2011년8월31일_winAPI_두 개의 타이머, 백그라운드 작업, 콜백함수(CALLBACK)




● 두 개의 타이머 (windows API정복 page.103)

   1: // winAPI정복 page.103 예제: TwoTimer
   2: // 2011년 8월 31일 작성 
   3:  
   4: /* 윈도우즈 프로그래밍 순서 WinMain()
   5: 
   6:     WndClass정의
   7: 
   8:     클래스를 등록
   9: 
  10:     메모리상에 윈도우 생성
  11: 
  12:     윈도우를 화면에 출력
  13: 
  14:     메시지 루프 (반복)    여기서 텍스트 출력할 것.
  15: */
  16:  
  17: #include <windows.h>        // 모든 API함수들의 원형과 사용하는 상수들이 정의되어 있음.
  18:  
  19: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    //메시지처리함수
  20: void MyTextOut(HDC hdc, int x, int y, LPCTSTR Text);
  21:  
  22: HINSTANCE g_hlnst;    //핸들 인스턴스
  23: LPCTSTR lpszClass = TEXT("TwoTimer");        //윈도우 제목으로 사용되는 const char * (문자열)
  24:  
  25: /***************************************************************************************************************************
  26: WinMain은 엔트리 포인트(시작함수)
  27: APIENTRY는 윈도우즈 표준호출규약 _stdcall(없어도 무방함)
  28: 
  29: WinMain의 인자들...
  30: ① hInstance: 프로그램의 인스턴스 핸들
  31: ② hPrevInstance: 바로 앞에 실행된 현재 프로그램의 인스턴스 핸들로 16비트와 호환성위한 것이니 무시하자. Win32는 NULL이다.
  32: ③ lpszCmdParam: 명령행으로 입력된 프로그램의 인수 (콘솔app에서 argv), 보통 실행직후에 열 파일의 경로가 전달됨.
  33: ④ nCmdShow: 프로그램이 실행될 형태이며 최소화, 보통 모양 등이 전달된다.
  34: ***************************************************************************************************************************/
  35: int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
  36: {
  37:     HWND hWnd;
  38:     MSG Message;
  39:     WNDCLASS WndClass;
  40:     g_hlnst = hInstance;
  41:  
  42:     WndClass.cbClsExtra = 0;
  43:     WndClass.cbWndExtra = 0;
  44:     WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  45:     WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  46:     WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  47:     WndClass.hInstance = hInstance;
  48:     WndClass.lpfnWndProc = WndProc;
  49:     WndClass.lpszClassName = lpszClass;
  50:     WndClass.lpszMenuName = NULL;
  51:     WndClass.style = CS_HREDRAW | CS_VREDRAW;
  52:     RegisterClass(&WndClass);
  53:  
  54:     
  55:     hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
  56:         //CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  57:         200, 200, 500, 200,
  58:         NULL, (HMENU)NULL, hInstance, NULL);
  59:     
  60:     ShowWindow(hWnd, nCmdShow);
  61:  
  62:     while(GetMessage(&Message, NULL, 0, 0)) {
  63:         TranslateMessage(&Message);
  64:         DispatchMessage(&Message);
  65:     }
  66:  
  67:     return (int)Message.wParam;
  68: }
  69:  
  70: LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
  71: {
  72:     HDC hdc;
  73:     PAINTSTRUCT ps;
  74:     SYSTEMTIME st;
  75:     static TCHAR sTime[128];
  76:     static RECT rt = {100, 100, 500, 120};
  77:     static int iCount = 0;
  78:     TCHAR sBuff[10];
  79:  
  80:     switch(iMessage) {
  81:     case WM_CREATE:
  82:         SetTimer(hWnd, 1, 1000, NULL);            //타이머 ID = 1, 1000ms후에 WM_TIMER 메시지발생.
  83:         SetTimer(hWnd, 2, 5000, NULL);            //타이머 ID = 2, 5000ms후에 WM_TIMER 메시지발생.
  84:         SendMessage(hWnd, WM_TIMER, 1, 0);        //WM_TIMER이벤트(ID = 1) 강제발생.
  85:         SendMessage(hWnd, WM_TIMER, 2, 0);        //WM_TIMER이벤트(ID = 2) 강제발생.
  86:         return 0;
  87:  
  88:     case WM_TIMER:
  89:         switch(wParam) {
  90:         case 1:                //타이머 ID 1
  91:             GetLocalTime(&st);
  92:             wsprintf(sTime, TEXT("%d년 %d월 %d일 지금 시간은 %d:%d:%d입니다."),
  93:                 st.wYear, st.wMonth, st.wDay, st.wHour, st.wMinute, st.wSecond);
  94:             InvalidateRect(hWnd, &rt, TRUE);
  95:             break;
  96:  
  97:         case 2:                //타이머 ID 2
  98:             MessageBeep(0);            
  99:             wsprintf(sBuff, "%d", iCount);
 100:             SetWindowText(hWnd, TEXT(sBuff));
 101:             ++iCount;
 102:             break;
 103:         }
 104:         return 0;
 105:  
 106:     case WM_PAINT:
 107:         hdc = BeginPaint(hWnd, &ps);
 108:         MyTextOut(hdc, 100, 100, sTime);
 109:         EndPaint(hWnd, &ps);
 110:         return 0;
 111:  
 112:     case WM_DESTROY:
 113:         KillTimer(hWnd, 1);
 114:         KillTimer(hWnd, 2);
 115:         PostQuitMessage(0);
 116:         return 0;
 117:     }
 118:     
 119:     return(DefWindowProc(hWnd, iMessage, wParam, lParam));
 120: }
 121:  
 122: void MyTextOut(HDC hdc, int x, int y, LPCTSTR Text)
 123: {
 124:     TextOut(hdc, x, y, Text, strlen(Text));
 125: }


<실행화면>

image


<소스설명>

  82:         SetTimer(hWnd, 1, 1000, NULL);            //타이머 ID = 1, 1000ms후에 WM_TIMER 메시지발생.
  83:         SetTimer(hWnd, 2, 5000, NULL);            //타이머 ID = 2, 5000ms후에 WM_TIMER 메시지발생.
  84:         SendMessage(hWnd, WM_TIMER, 1, 0);        //WM_TIMER이벤트(ID = 1) 강제발생.

SetTimer()로 현재 윈도우에 타이머 ID 1번과 2번을 설정함.
ID 1번은 1000ms(1초)마다,
ID 2번은 5000ms(5초)마다 WM_TIMRE 메시지가 발생한다.

SendMessage()로 타이머 ID 1번만 최초실행시(WM_CREATE) 메시지를 강제로 발생시켜 ID 1번에 해당하는 기능을 수행하도록함.

  85:         SendMessage(hWnd, WM_TIMER, 2, 0);        //WM_TIMER이벤트(ID = 2) 강제발생.

추가로 2번 타이머도 실행시 바로 메시지가 발생할 수 있도록함.

  88:     case WM_TIMER:
  89:         switch(wParam) {
  90:         case 1:                //타이머 ID 1
  97:         case 2:                //타이머 ID 2

WM_TIMER메시지가 발생한 후 wParam에 들어 있는 값을 확인해 보면 어느 타이머가 알려 왔는지 알 수 있다.

  98:             MessageBeep(0);            
  99:             wsprintf(sBuff, "%d", iCount);
 100:             SetWindowText(hWnd, TEXT(sBuff));
 101:             ++iCount;

MessageBeep()는 사운드코덱(사운드카드)를 통해 소리를 발생시키는 함수로 0을 인자로 넣으면 띵~ 하는 소리가 난다.
이어폰이 없으면 확인하기 어려우니 아래와 같이,
wsprintf()로 정수형 변수의 값을 서식화해 SetWindowText()로 윈도우의 타이틀바에 그 값이 표시되도록 한다.


★ Win32환경에서 만들 수 있는 타이머의 게수에는 제한이 없지만 그렇다고 많이 만들면 느려진다.






● 백그라운드 작업 (windows API page.105)

지속적인 작업을 하기 위해 보통 무한루프를 돌린다.
그러나 윈도우즈와 같은 멀티태스킹환경에서는 이런 방식을 사용해서는 안 된다. 왜냐하면 한 프로그램이 제어권을 독점하고 있어서는 안 되며 다른 프로그램도 실행시간을 가져야 하기 때문이다.
CPU를 독점하는 이런 무한 루프를 작성해서는 안 되며 반드시 메시지가 전달되었을 때 에 한해 필요한 작업을 해야 한다.

<예제코드>

   1: // winAPI정복 page.105 예제: RandGrp
   2: // 2011년 8월 31일 작성 
   3:  
   4: /* 윈도우즈 프로그래밍 순서 WinMain()
   5: 
   6:     WndClass정의
   7: 
   8:     클래스를 등록
   9: 
  10:     메모리상에 윈도우 생성
  11: 
  12:     윈도우를 화면에 출력
  13: 
  14:     메시지 루프 (반복)    여기서 텍스트 출력할 것.
  15: */
  16:  
  17: #include <windows.h>        // 모든 API함수들의 원형과 사용하는 상수들이 정의되어 있음.
  18:  
  19: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    //메시지처리함수
  20: void MyTextOut(HDC hdc, int x, int y, LPCTSTR Text);
  21:  
  22: HINSTANCE g_hlnst;    //핸들 인스턴스
  23: LPCTSTR lpszClass = TEXT("RandGrp");        //윈도우 제목으로 사용되는 const char * (문자열)
  24:  
  25: /***************************************************************************************************************************
  26: WinMain은 엔트리 포인트(시작함수)
  27: APIENTRY는 윈도우즈 표준호출규약 _stdcall(없어도 무방함)
  28: 
  29: WinMain의 인자들...
  30: ① hInstance: 프로그램의 인스턴스 핸들
  31: ② hPrevInstance: 바로 앞에 실행된 현재 프로그램의 인스턴스 핸들로 16비트와 호환성위한 것이니 무시하자. Win32는 NULL이다.
  32: ③ lpszCmdParam: 명령행으로 입력된 프로그램의 인수 (콘솔app에서 argv), 보통 실행직후에 열 파일의 경로가 전달됨.
  33: ④ nCmdShow: 프로그램이 실행될 형태이며 최소화, 보통 모양 등이 전달된다.
  34: ***************************************************************************************************************************/
  35: int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
  36: {
  37:     HWND hWnd;
  38:     MSG Message;
  39:     WNDCLASS WndClass;
  40:     g_hlnst = hInstance;
  41:  
  42:     WndClass.cbClsExtra = 0;
  43:     WndClass.cbWndExtra = 0;
  44:     WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  45:     WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  46:     WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  47:     WndClass.hInstance = hInstance;
  48:     WndClass.lpfnWndProc = WndProc;
  49:     WndClass.lpszClassName = lpszClass;
  50:     WndClass.lpszMenuName = NULL;
  51:     WndClass.style = CS_HREDRAW | CS_VREDRAW;
  52:     RegisterClass(&WndClass);
  53:  
  54:     
  55:     hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
  56:         //CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
  57:         200, 200, 500, 400,
  58:         NULL, (HMENU)NULL, hInstance, NULL);
  59:     
  60:     ShowWindow(hWnd, nCmdShow);
  61:  
  62:     while(GetMessage(&Message, NULL, 0, 0)) {
  63:         TranslateMessage(&Message);
  64:         DispatchMessage(&Message);
  65:     }
  66:  
  67:     return (int)Message.wParam;
  68: }
  69:  
  70: LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
  71: {
  72:     HDC hdc;
  73:     int i;
  74:  
  75:     switch(iMessage) {
  76:     case WM_CREATE:
  77:         SetTimer(hWnd, 1, 50, NULL);            //타이머 ID = 1, 50ms후에 WM_TIMER 메시지발생.
  78:         return 0;
  79:  
  80:     case WM_TIMER:
  81:         hdc = GetDC(hWnd);
  82:  
  83:         for(i = 0 ; i < 1000 ; ++i) {            //좌표(0~499, 0~399)에 알록달록 점을 1000개 그린다.
  84:             SetPixel(hdc, rand() % 500, rand() % 400,
  85:                 RGB(rand() % 256, rand() % 256, rand() % 256));
  86:         }
  87:         ReleaseDC(hWnd, hdc);
  88:         return 0;
  89:  
  90:     case WM_LBUTTONDOWN:        //왼쪽 클릭시 마우스 좌표에 반지름이 10인 원을 그린다.
  91:         hdc = GetDC(hWnd);
  92:         Ellipse(hdc, LOWORD(lParam) - 10, HIWORD(lParam) - 10,
  93:             LOWORD(lParam) + 10, HIWORD(lParam) + 10);
  94:         ReleaseDC(hWnd, hdc);
  95:         return 0;
  96:  
  97:     case WM_DESTROY:
  98:         KillTimer(hWnd, 1);
  99:         PostQuitMessage(0);
 100:         return 0;
 101:     }
 102:     
 103:     return(DefWindowProc(hWnd, iMessage, wParam, lParam));
 104: }
 105:  
 106: void MyTextOut(HDC hdc, int x, int y, LPCTSTR Text)
 107: {
 108:     TextOut(hdc, x, y, Text, strlen(Text));
 109: }

<실행화면>

image


<소스설명>

  80:     case WM_TIMER:
  83:         for(i = 0 ; i < 1000 ; ++i) {            //좌표(0~499, 0~399)에 알록달록 점을 1000개 그린다.
  90:     case WM_LBUTTONDOWN:        //왼쪽 클릭시 마우스 좌표에 반지름이 10인 원을 그린다.

타이머를 돌려 WM_TIMER메시지를 받아 50ms마다 랜덤좌표에 알록달록 점을 1000개 그린다.
타이머는 항상 돌아 화면 점을 뿌린다. 점을 뿌리는 도중에 마우스메시지를 받아 다른 일도 처리할 수 있다.
스타크래프투게임에서 SVC가 미네랄을 계속 채집하여 나르는 일은 타이머메시지에 넣으면 되겠다.

 

● 콜백함수

SetTimer()의 네 번째 인수는 TIMERPROC lpTimerFunc이라고 되어 있는데 이 인수는 타이머 프로시저 함수의 포인터를 가리킨다.
이 인수가 NULL로 되어 있을 경우 첫 번째 인수로 지정된 hWnd로 WM_TIMER메시지가 전달되지만 이 인수에 타이머 함수가 지정되었을 경우는 매 시간마다 이 함수가 대신 호출된다.
골백함수의 원형은 아래와 같다.

    void CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime);

콜백함수의 이름은 상기와 같이 TimerProc가 아니어도 되나 SetTimer() 호출 시 네 번째 인수에 이름과는 같아야 한다.
그리고 인자들의 type은 꼭 같아야 한다.

hWnd는 타이머를 소유한 윈도우의 핸들이고,
uMsg는 WM_TIMER,
idEvent는 타이머 ID,
dwTIme은 윈도우즈가 실행된 후의 경과시간이다.
이 함수의 인수들은 잘 사용되지 않으므로 구체적으로 알 필요는 없다. 알고 싶어~


<예제코드>

   1: // winAPI정복 page.108 예제: Callback
   2: // 2011년 8월 31일 작성 
   3:  
   4: /* 윈도우즈 프로그래밍 순서 WinMain()
   5: 
   6:     WndClass정의
   7: 
   8:     클래스를 등록
   9: 
  10:     메모리상에 윈도우 생성
  11: 
  12:     윈도우를 화면에 출력
  13: 
  14:     메시지 루프 (반복)    여기서 텍스트 출력할 것.
  15: */
  16:  
  17: #include <windows.h>        // 모든 API함수들의 원형과 사용하는 상수들이 정의되어 있음.
  18:  
  19: LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);    //메시지처리함수
  20: void MyTextOut(HDC hdc, int x, int y, LPCTSTR Text);
  21:  
  22: HINSTANCE g_hlnst;    //핸들 인스턴스
  23: LPCTSTR lpszClass = TEXT("Callback");        //윈도우 제목으로 사용되는 const char * (문자열)
  24:  
  25: /***************************************************************************************************************************
  26: WinMain은 엔트리 포인트(시작함수)
  27: APIENTRY는 윈도우즈 표준호출규약 _stdcall(없어도 무방함)
  28: 
  29: WinMain의 인자들...
  30: ① hInstance: 프로그램의 인스턴스 핸들
  31: ② hPrevInstance: 바로 앞에 실행된 현재 프로그램의 인스턴스 핸들로 16비트와 호환성위한 것이니 무시하자. Win32는 NULL이다.
  32: ③ lpszCmdParam: 명령행으로 입력된 프로그램의 인수 (콘솔app에서 argv), 보통 실행직후에 열 파일의 경로가 전달됨.
  33: ④ nCmdShow: 프로그램이 실행될 형태이며 최소화, 보통 모양 등이 전달된다.
  34: ***************************************************************************************************************************/
  35: int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
  36: {
  37:     HWND hWnd;
  38:     MSG Message;
  39:     WNDCLASS WndClass;
  40:     g_hlnst = hInstance;
  41:  
  42:     WndClass.cbClsExtra = 0;
  43:     WndClass.cbWndExtra = 0;
  44:     WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
  45:     WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
  46:     WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
  47:     WndClass.hInstance = hInstance;
  48:     WndClass.lpfnWndProc = WndProc;
  49:     WndClass.lpszClassName = lpszClass;
  50:     WndClass.lpszMenuName = NULL;
  51:     WndClass.style = CS_HREDRAW | CS_VREDRAW;
  52:     RegisterClass(&WndClass);
  53:  
  54:     
  55:     hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
  56:         //CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,        
  57:         200, 200, 500, 400,
  58:         NULL, (HMENU)NULL, hInstance, NULL);
  59:     
  60:     ShowWindow(hWnd, nCmdShow);
  61:  
  62:     while(GetMessage(&Message, NULL, 0, 0)) {
  63:         TranslateMessage(&Message);
  64:         DispatchMessage(&Message);
  65:     }
  66:  
  67:     return (int)Message.wParam;
  68: }
  69:  
  70: //운영체제가 호출하는 CALLBACK함수 (타이머메시지처리), 윈도우핸들, 
  71: void CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime)
  72: {
  73:     HDC hdc;
  74:     int i;
  75:  
  76:     hdc = GetDC(hWnd);
  77:     for(i = 0 ; i < 1000 ; ++i) {        //알록달록 랜덤좌표에 점 1000개 
  78:         SetPixel(hdc, rand() % 500, rand() % 400,
  79:             RGB(rand() % 256, rand() % 256, rand() %256));
  80:     }
  81:     ReleaseDC(hWnd, hdc);
  82:     
  83:     return ;
  84: }
  85:  
  86:  
  87: //운영체제가 호출하는 CALLBACK함수 (메시지 처리)
  88: LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
  89: {
  90:     HDC hdc;
  91:     int i;
  92:  
  93:     switch(iMessage) {
  94:     case WM_CREATE:
  95:         SetTimer(hWnd, 1, 50, TimerProc);            //타이머 ID = 1, 50ms후에 TimerProc()를 OS가 자동호출.
  96:         //SendMessage(hWnd, WM_TIMER, 1, 0);        //강제 함수호출은 안 됨. 
  97:         return 0;
  98:  
  99:     case WM_LBUTTONDOWN:
 100:         hdc = GetDC(hWnd);
 101:         Ellipse(hdc, LOWORD(lParam) - 10, HIWORD(lParam) - 10,
 102:             LOWORD(lParam) + 10, HIWORD(lParam) + 10);
 103:         ReleaseDC(hWnd, hdc);
 104:         return 0;
 105:  
 106:     case WM_DESTROY:
 107:         KillTimer(hWnd, 1);
 108:         PostQuitMessage(0);
 109:         return 0;
 110:     }
 111:     
 112:     return(DefWindowProc(hWnd, iMessage, wParam, lParam));
 113: }
 114:  
 115: void MyTextOut(HDC hdc, int x, int y, LPCTSTR Text)
 116: {
 117:     TextOut(hdc, x, y, Text, strlen(Text));
 118: }


<실행화면>

image


<소스설명>

  94:     case WM_CREATE:
  95:         SetTimer(hWnd, 1, 50, TimerProc);            //타이머 ID = 1, 50ms후에 TimerProc()를 OS가 자동호출.
  96:         //SendMessage(hWnd, WM_TIMER, 1, 0);        //강제 함수호출은 안 됨. 

WM_CREATE메시지로 최초실행시 SetTimer()로 타이머를 돌리는데 4번째 인수로 NULL이 아닌,
TImerProc()의 이름을 넣어 콜백함수로 등록하였다.
콜백함수가 등록되면 운영체제는 윈도우로 WM_TIMER 메시지로 보내는 대신 이 함수를 주기적으로 호출한다.

  70: //운영체제가 호출하는 CALLBACK함수 (타이머메시지처리), 윈도우핸들, 
  71: void CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime)

콜백함수 내부에서는 RandGrp.cpp와 같이 (0~499, 0~399)의 좌표에 알록달록 점을 1000개 찍는 일을 한다.
 
이와 같이 콜백함수로 처리하는 방법과 RandGrp.cpp와 같이 WM_TIMER에서 처리는 방법의 차이점은,
WM_TIMER메시지는 다른 메시지가 있을 경우 실행 순서에 밀려 늦게 호출되는 경우가 있지만,
콜백함수를 사용하면 정확한 시간에 호출된다는 점이다. 그래서 정확도를 요하는 작업은 타이머 메시지보다는 콜백함수를 사용하는 것이 더 좋다고 되어 있다. 하지만 Win32 환경에서는 사실상 별 차이가 없다.
콜백함수는 어떻게 하면 WM_CREATE메시지에서 호출되게 할 수 있을까? 궁금하네…


★ 콜백함수란?

image 

일반적으로 API함수들은 운영체제가 제공하며 프로그램에서는 이 함수들을 호출해서 운영체제의 서비스를 받는다.
예를 들어 도스의 시스템 콜 함수를 호출하여 디스크 입출력을 한다든가 윈도우즈의 TextOut()를 호출하여 문자열을 출력하는 경우가 이에 해당한다. 응용 프로그램이 운영체제에 내장도니 함수를 호출하여 원하는 작업을 하는 것이다.

반면 콜백함수는 응용 프로그램이 제공하며 운영체제가 필요할 때 호출하는 함수이다.
호출되는 방향이 거꾸로 되었기 때문에 콜백(CALLBACK)이라고 부르는 것이다.