Notice
Recent Posts
Recent Comments
Link
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

codingfarm

4. 타이머, 콜백함수 본문

Windows/윈도우즈 API

4. 타이머, 콜백함수

scarecrow1992 2021. 1. 10. 20:03

타이머 메시지인 WM_TIMER는 사용자의 동작과 관계없이 발생하는 메시지이다. 이 메시지는 한번 지정해 놓으면 일정한 시간 간격을 두고 연속적으로 계속 발생한다. 주기적으로 같은 동작을 반복해야 하거나 여러번 나누어 해야할 일이 있을 때 이 메시지를 사용한다

 


시계

아래는 타이머 메시지를 이용한 간단한 시계이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("MyTimer");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    SYSTEMTIME st;
    time_t mytime;
    static HANDLE hTimer;
    //static LPCTSTR str;
    static TCHAR sTime[128];
    switch (iMessage) {
    case WM_CREATE:
        hTimer = (HANDLE)SetTimer(hWnd, 11000NULL);
        sTime[0= 0;
        return 0;
    case WM_TIMER:
        GetLocalTime(&st);
        wsprintf(sTime, TEXT("this time is %d:%d:%d"), st.wHour, st.wMinute, st.wSecond);
        InvalidateRect(hWnd, NULL, TRUE);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 100100, sTime, lstrlen(sTime));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        KillTimer(hWnd, 1);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

화면에 현재 시각이 1초에 한번씩 갱신되며 출력됨을 볼 수 있다.

현재시간을 구할때는 SYSTEMTIME 구조체와 GetLocalTime 이라는 함수를 사용하는데 이들에 대한 상세한 분석은 우선 무시한다. 시간에 관련된 API에 대해서는 다음기회에 체계적으로 다룰것이다.

WndProc의 선두에는 시간값을 조사할 st 구조체와 이 시간값을 문자여로 변경하여 저장할 sTime 문자열이 선언되어있는데 sTime은 조사해 놓은 시간을 기억해야 하므로 정적 변수로 선언했다.

 

WndProc에서 첫번째로 처리하는 메시지는 WM_CREATE 메시지이다. 이는 윈도우가 처음 생성될때 발생하는데 이 메시지에서 프로그램 시작시 꼭 한번만 초기화해야하 할 처리를 한다. 프로그램 실행에 필요한 메모리의 할당이라던가, 전역변수에 초기값을 대입하는 등의 초기화 처리가 보통 WM_CREATE에서 이루어 진다.

이예제에서는 WM_CREATE 메시지에서 SetTimer 함수를 사용하여 타이머를 생성했다. 즉, 윈도우가 만들어질 때 타이머가 생성된다.

UINT_PTR SetTimer( HWND hWnd, UINT_PTR nIDEvent, UINT uElapse, TIMERPROC lpTimerFunc );
매개변수 기능
hWnd $\bullet$ 타이머 메시지를 받을 윈도우
$\bullet$ 통상 설치하는 윈도우가 메시지를 받으므로 WndProc의 인수로 전달되는 hWnd를 그대로 쓴다.
nIDEvent $\bullet$ 타이머의 번호를 지정한다.
$\bullet$ 하나만 사용하면 1, 여러개를 쓸경우 nIDEvent에 겹처지지 않게 부여한다.
가령 세개의 타이머를 사용한다면 각각 1,2,3의 타이머 번호를 지정한다.
타이머 번호는 WM_TIMER 메시지에서 타이머를 구분하기 위한 표식으로 사용한다.
uElapse $\bullet$ 타이머의 주기를 설정한다.(단위 : $1ms$)
$\bullet$ 여기서 설정한 주기에 맞추어 타이머 메시지가 hWnd로 전달된다.
$\bullet$ 타이머에서 설정가능한 최대, 최소 주기에는 한계가 있다.

lpTimerFunc $\bullet$ 타이머 메시지가 발생할 때마다 호출될 함수를 지정한다.
$\bullet$ 사용하지 않을경우 NULL로 설정

반환값 : 타이머를 소유하는 윈도우 없이 타이머가 만들어젓을 경우(첫번째 인수가 NULL일 경우)에 한해 사용되는ㄴ것이나 거의 사용되진 않는다.

 

이 예제에서는 ID 1번으로 1초에 한번씩 타이머 메시지를 hWnd로 보내도록 설정하였다. 이제 1초에 한 번씩 hWnd 윈도우에 WM_TIMER 메시지가 전달될 것이다.

 

WM_TIMER 메시지는 wParam으로 타이머 ID를 전달받으며 lParam으로 타이머 메시지 발생시 호출될 함수의 번지가 전달된다.

 

WM_TIMER 메시지가 발생하면 GetLocalTime 함수로 시간을 조사한 후, 출력을 위해 sTime 문자열로 변환해둔다. 그리고 화면을 갱신하기 위해 InvalidateRect 함수를 호출한다. 그러면 WM_PAINT 메시지에서 이 문자열을 화면으로 출력한다.

마지막으로는 윈도우가 파괴될 때 보내지는 WM_DESTROY 메시지에서 설치된 타이머를 제거하는 것이다. 이 대는 KillTimer 함수를 사용한다.

BOOL KillTimer( HWND hWnd, UINT_PTR uIDEvent );

타이머는 시스템 전역자원 이므로 더이상 필요가 없어지면 파괴하는것이 좋다.

매개변수 기능
hWnd 파괴할 타이머를 소유한 윈도우 핸들
uIDEvent 파괴할 타이머의 ID

 


Send/Message

앞서 만든 프로그램에는 여러가지 문제점들이 있다.

1. 프로그램 실행 이후 1초 후에 시간이 보인다.

WM_PAINT 메시지를 호출하기 위해선 우선 WM_TIMER 메시지가 호출되어야 하는데 최초의 메시지가 프로그램 시작 1초후에 호출되기 때문이다. 이를 위해선 프로그램 시작 직후에 WM_TIMER 메시지를 강제로 발생시켜 시간을 먼저 조사해야한다. 이때 쓰이는 함수가 SendMessage 이다.

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

이는 메시지를 강제로 발생시키기 위해 사용되는 함수이다. 이함수를 사용하여 hWnd 윈도우로 Msg 메시지를 보내면 된다. 그러면 hWnd는 Msg 메시지가 발생한것으로 인식하고 필요한 처리를 하게 될 것이다.

세번째 네번째 함수는 메시지의 추가 정보인 wParam, lParam이다.

반환값은 메시지 처리함수가 리턴하는 값이며 메시지에 따라 해석방법이 다르다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("MyTimer");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    SYSTEMTIME st;
    time_t mytime;
    static HANDLE hTimer;
    //static LPCTSTR str;
    static TCHAR sTime[128];
    switch (iMessage) {
    case WM_CREATE:
        hTimer = (HANDLE)SetTimer(hWnd, 11000NULL);
        sTime[0= 0;
        SendMessage(hWnd, WM_TIMER, 10);
        return 0;
    case WM_TIMER:
        GetLocalTime(&st);
        wsprintf(sTime, TEXT("this time is %d:%d:%d"), st.wHour, st.wMinute, st.wSecond);
        InvalidateRect(hWnd, NULL, TRUE);
        return 0;
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 100100, sTime, lstrlen(sTime));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        KillTimer(hWnd, 1);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

WM_CREATE 끝부분에 SendMessage 함수를 추가하여서 프로그램이 시작하자마자 타이머가 출력된것을 볼 수 있다.

 

두번째 문재점. 화면 깜빡거림

윈도우10 환경에서는 개선된것인지 모르겠지만 저자에 의하면 화면이 깜빡거린다고 한다.

이는 WM_TIMER 메시지에서 시간을 변경한 후 화면을 다시 그리기 위해 다음 함수를 호출하기 때문이다.

InvalidateRect(hWnd, NULL, TRUE);

두번째 인수가 NULL이므로 화면 전체가 무효화되며 세번째 인수가 TRUE 이므로 일단 화면을 지운 후 다시 그린다. 화면전체를 통째로 다시 그리기 보다는 무효화할 영역을 두번째 인수로 전달하여 일부분만 무효화를 진행하여 최대한 빨리 윈도우를 다시 그리도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("MyTimer");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    SYSTEMTIME st;
    time_t mytime;
    static HANDLE hTimer;
    //static LPCTSTR str;
    static TCHAR sTime[128];
    static RECT rt = { 100100400120 };
    switch (iMessage) {
    case WM_CREATE:
        hTimer = (HANDLE)SetTimer(hWnd, 11000NULL);
        sTime[0= 0;
        SendMessage(hWnd, WM_TIMER, 10);
        return 0;
    case WM_TIMER:
        GetLocalTime(&st);
        wsprintf(sTime, TEXT("this time is %d:%d:%d"), st.wHour, st.wMinute, st.wSecond);
        InvalidateRect(hWnd, &rt, TRUE);
        return 0
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 100100, sTime, lstrlen(sTime));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        KillTimer(hWnd, 1);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

WndProc 초반부에 rt 구조체로 범위를 설정해주고 InvalidateRect에 이 구조체 주소를 전달함으로써 일부분만 무효화가 이루어지게끔 하였다.

 


두개의 타이머

한번에 여러개의 타이머를 설치하여 사용할 수 있다. SetTimer를 호출하되 두번째 인수인 타이머 ID를 각가 다르게 설정해야 한다. 만약 같은 ID로 타이머를 또 설치하면 이때는 기존 타이머의 시간 간격이 변경된다.

어떤 타이머에 의해 WM_TIMER 메시지가 발생했는지는 wParam으로 전달되는 타이머의 ID로 구분한다. 이 wParam 값은 SetTimer 함수가 타이머를 설치할 때 두번째 인수로 지정한 값이다.

 

아래는 5초에 한번씩 비프음이 울리게 하는 소스이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("MyTimer");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, 000)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    SYSTEMTIME st;
    static TCHAR sTime[128];
    static RECT rt = { 100100400120 };
    switch (iMessage) {
    case WM_CREATE:
        SetTimer(hWnd, 11000NULL);
        SetTimer(hWnd, 25000NULL);
        SendMessage(hWnd, WM_TIMER, 10);
        SendMessage(hWnd, WM_TIMER, 20);
        return 0;
    case WM_TIMER:
        switch (wParam) {
        case 1:
            GetLocalTime(&st);
            wsprintf(sTime, TEXT("this time is %d:%d:%d"), st.wHour, st.wMinute, st.wSecond);
            InvalidateRect(hWnd, &rt, TRUE);
            break;
 
        case 2:
            MessageBeep(0);
            break;
        }
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 100100, sTime, lstrlen(sTime));
        EndPaint(hWnd, &ps);
        return 0;
    case WM_DESTROY:
        KillTimer(hWnd, 1);
        KillTimer(hWnd, 2);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

타이머의 갯수에 제한은 없지만 하드웨어의 성능에 따라 적절한 숫자로 운영해야한다.

16비트 운영체제에서는 최대 16개까지 허용되었다.

 


백그라운드 작업

프로그램이 실행되는동안 지속적으로 수행해야할 작업이 있을경우

for(;;){
    지속적인 작업
    기타 작업
}

위처럼 무한루프를 사용하는 방법이 있지만 윈도우즈와 같은 멀티 태스킹 환경에서는 이런 방식을 사용해선 안된다.

반드시 메시지가 전달되었을 때에 한해 필요한 작업을 해야한다. 이럴때 사용하는 메시지가 바로 타이머 메시지인데 타이머로 백그라운드 작업을 하는 예제는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("MyTimer");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, NULL00)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    int i;
    switch (iMessage) {
    case WM_CREATE:
        SetTimer(hWnd, 150NULL);
        SendMessage(hWnd, WM_TIMER, 10);
        return 0;
    case WM_TIMER:
        hdc = GetDC(hWnd);
        for (i = 0; i < 1000; i++) {
            SetPixel(hdc, rand() % 500, rand() % 400
                RGB(rand() % 256, rand() % 256, rand() % 256));
        }
        ReleaseDC(hWnd, hdc);
        return 0;
 
    case WM_LBUTTONDOWN:
        hdc = GetDC(hWnd);
        Ellipse(hdc, LOWORD(lParam)-10, HIWORD(lParam)-10,
            LOWORD(lParam) + 10, HIWORD(lParam) + 10);
        ReleaseDC(hWnd, hdc);
        return 0;
    case WM_DESTROY:
        KillTimer(hWnd, 1);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

초당 20번 주기의 타이머를 설치하고, 주기마다 1000개의 점을 난수로 얻은 임의 좌표에 임의 색상으로 출력하고, 좌클릭 하는 지점에는 하얀 원이 그려지게끔 하는 윈도우이다.

점이 찍히는 작업을 타이머를 이용하여 백그라운드에서 작동하게끔 함으로서 좌클릭을 통한 작업 또한 병렬적으로 실행 가능하게끔 하였다.

만약 이런 백그라운드 작업을 for(;;)의 무한루프를 이용하여 구현했다면 좌클릭을 통해 원을 찍는 작업 또한 막힐것이며, WM_PAINT에서 벗어날 수 없으므로 종료 버튼을 눌러도 종료조차 되지 않는다.

 

사실 이런 백그라운드 작업 및 병령실행을 시행할 제일 나은 방법은 스레드를 이용하는것이다.

 


콜백 함수

이전예제에서는 타이머메시지를 이용하여 백그라운드 작업을 구현했는데

이는 콜백함수로도 해결할 수 있다.

SetTimer의 네 번째 인수는 TIMERPROC lpTimerFunc라고 되어 있는데 이 인수는 타이머 프로시저 함수의 포인터를 가리킨다. 이 인수가 NULL로 되어 있으면 첫 번째 인수로 지정된 hWnd로 WM_TIMER 메시지가 전달되지만 이 인수에 타이머 함수가 지정되었을 경우는 매 시간마자 이 함수가 대신 호출된다. 즉, 타이머 함수가 지정되면 메시지를 보내는 대신 함수를 호출한다.

타이머 함수의 원형은 아래와 같은 형태로 작성되어야 한다.

void CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime);
매개변수 기능
hWnd 타이머를 소유한 윈도우의 핸들
uMsg WM_TIMER
idEvent 타이머 id
dwTime 윈도우즈가 실행된 후의 경과 시간

이를 활용한 예제 코드는 아래와 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("MyTimer");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, NULL00)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
void CALLBACK TimerProc(HWND hWnd, UINT uMsg, UINT idEvent, DWORD dwTime)
{
    HDC hdc;
    int i;
    hdc = GetDC(hWnd);
    for (i = 0; i < 1000; i++)
        SetPixel(hdc, rand() % 500, rand() % 400,
            RGB(rand() % 256, rand() % 256, rand() % 256, ));
    ReleaseDC(hWnd, hdc);
}
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    switch (iMessage) {
    case WM_CREATE:
        SetTimer(hWnd, 150, (TIMERPROC)TimerProc);
        return 0;
    case WM_LBUTTONDOWN:
        hdc = GetDC(hWnd);
        Ellipse(hdc, LOWORD(lParam) - 10, HIWORD(lParam) - 10,
            LOWORD(lParam) + 10, HIWORD(lParam) + 10);
        ReleaseDC(hWnd, hdc);
        return 0;
    case WM_DESTROY:
        KillTimer(hWnd, 1);
        PostQuitMessage(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

WndProc의 WM_CREATE에서 타이머를 설치하되 네 번째 인수로 TimerProc 함수를 콜백으로 지정했다.

콜백함수가 등록되면 운영체제는 윈도우로 WM_TIMER 메시지를 보내는 대신 이 함수를 주기적으로 호출한다.

프로그램의 실행결과는 이전 예제 코드와 완전 동일하다. 무작위로 점이 찍히며 점을 찍는 동안에도 마우스로 타원을 그릴 수 있다.

 

API 함수 콜백 함수
$\bullet$ OS에서 제공한다.
$\bullet$ 프로그램에서 호출하여 OS의 서비스를 받는다.

ex : 디스크 입출력, 문자열 출력...
$\bullet$ 응용프로그램에서 제공한다.
$\bullet$ OS가 필요할 때 호출한다.
$\bullet$ 함수원형이 정해저 있다.


ex : WndProc, TimerProc

 

 


일회용 타이머

  • 타이머는 윈도우의 주기적인 작업 외에 일회적인 대기 처리에도 흔히 쓰인다.
    • ex : 특정 사건 발생시 프로그램의 상태를 변경한 채 일정시간 머무르고 싶을 경우

가령, 특정 버튼을 눌렀을때, 문자열을 3초동안만 출력하고 싶으면 아래의 코드를 쓰면 될것이다.

1
2
3
4
5
case WM_LBUTTONDOWN:
    문자열 출력
    Sleep(3000);
    문자열 삭제
    return 0;
cs

우리가 원하는 기능 자체는 구현되었지만, 문자열이 표시되는 3초 동안은 윈도우를 이동할수도 없고, 키 입력에도 반응하지 않는다. 심지어 WM_PAINT 메시지도 처리하지 못하므로 3초 내에 다른 윈도우가 이 윈도우를 가리면 문자열을 다시 그리지도 못한다. 즉, 바람직하지 못한 방법이다.

 

타이머를 통해 해결해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
#include <windows.h>
 
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("MyTimer");
 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance
    , LPSTR lpszCmdParam, int nCmdShow)
{
    HWND hWnd;
    MSG Message;
    WNDCLASS WndClass;
    g_hInst = hInstance;
 
    WndClass.cbClsExtra = 0;
    WndClass.cbWndExtra = 0;
    WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
    WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    WndClass.hInstance = hInstance;
    WndClass.lpfnWndProc = (WNDPROC)WndProc;
    WndClass.lpszClassName = lpszClass;
    WndClass.lpszMenuName = NULL;
    WndClass.style = CS_HREDRAW | CS_VREDRAW;
    RegisterClass(&WndClass);
 
    hWnd = CreateWindow(lpszClass, lpszClass, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, (HMENU)NULL, hInstance, NULL);
    ShowWindow(hWnd, nCmdShow);
 
    while (GetMessage(&Message, NULL00)) {
        TranslateMessage(&Message);
        DispatchMessage(&Message);
    }
    return Message.wParam;
}
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    HDC hdc;
    PAINTSTRUCT ps;
    static TCHAR str[128];
 
    switch (iMessage) {
    case WM_LBUTTONDOWN:
        lstrcpy(str, L"왼쪽 버튼을 눌렀습니다.");
        InvalidateRect(hWnd, NULL, TRUE);
        SetTimer(hWnd, 13000NULL);
        return 0;
    case WM_TIMER:
        KillTimer(hWnd, 1);
        lstrcpy(str, L"");
        InvalidateRect(hWnd, NULL, TRUE);
        return 0;
 
    case WM_PAINT:
        hdc = BeginPaint(hWnd, &ps);
        TextOut(hdc, 1010, str, lstrlen(str));
        EndPaint(hWnd, &ps);
        return 0;
 
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
    }
 
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

 

 

 

 

 

 

 

 

 

 

 

'Windows > 윈도우즈 API' 카테고리의 다른 글

5. 리소스  (0) 2021.05.01
4. 윈도우 관리 메시지  (0) 2021.04.29
4. 입력 - 마우스 입력  (0) 2021.01.10
4. 입력 - 키보드 입력  (0) 2020.11.30
3. 메시지 비프  (0) 2020.11.30
Comments