![[Windows API] Win32 API의 백그라운드 작업과 콜백 함수](https://img1.daumcdn.net/thumb/R750x0/?scode=mtistory2&fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs6Z9M%2Fbtrrfn8Y1SK%2FIitr58MbMpl8722K3l2KDk%2Fimg.png)
목표
Windows API를 활용해 백그라운드 작업을 하고, 콜백 함수에 대해서 이해하고 실습하도록 하겠습니다.
목차 클릭하면 해당 목차로 이동합니다.
개요
이전 포스팅에서 타이머에 대한 내용을 다뤘습니다. 타이머를 활용해서 백그라운드 작업을 할 수 있습니다. 또한, 타이머는 윈도우 프로시저 내에서 WM_TIMER 메세지를 처리하는 방식으로 활용했습니다. 작은 프로그램에선 상관이 없지만, 프로그램의 크기가 커진다면 윈도우 프로시저 내에서 모든 것을 처리한다면 복잡성이 올라가고 가독성도 떨어질 것입니다. 이번 포스팅에서는 타이머를 활용해 백그라운드 작업을 하는 것과 콜백 함수를 통해서 타이머를 처리하는 것에 대해서 알아보도록 하겠습니다.
백그라운드 작업
Windows API는 백그라운드 작업을 지원합니다. 어떤 작업을 처리하는 도중에 다른 메세지가 발생한다면 해당 메세지를 처리할 수 있어야 합니다. 발생한 메세지 중 필요한 작업을 해야한다는 뜻입니다. 이 말은, 한 프로그램이 제어권을 독점하고 있으면 안된다는 것입니다. 이를 테면, 무한루프가 발생하는 프로그램을 작성하는 것입니다. 반복문이 돌고 있는 도중에는 다른 작업을 할 수 없기 때문입니다. 이전 포스팅에서 배운 타이머를 이용해서 특정 작업이 리소스(자원)을 독점하는 것을 막을 수 있습니다. 간단한 예제를 통해서 확인해보도록 하겠습니다.
RandGrp 프로젝트
이번에 만들어볼 프로그램은 배경에 1000개의 랜덤 좌표에 임의의 색상을 출력하는 것입니다. 이 작업을 초당 20번 주기로 반복합니다. 점이 지글지글 찍히면서 티비의 채널이 끊겼을 때 나오는 화면과 비슷한 화면을 나타냅니다. 이와 동시에, 왼쪽 마우스 버튼을 클릭하면 원을 그리는 것을 작성해보도록 하겠습니다.
배경을 출력하기 위해 메세지를 처리하고 있는 도중에 마우스가 클릭되었다는 메세지가 발생하면 해당 메세지를 처리하는 것입니다.
프로그램이 시작되면 랜덤 좌표에 임의의 색깔의 원이 프로그램이 종료할 때까지 출력되어야 합니다. 끝도 없이 계속 진행되기 때문에 다음과 같이 생각할 수 있습니다.
case WM_PAINT:
hdc = BeginPaint(hWnd, &ps);
while(1) {
SetPixel(hdc, rand()%500, rand()%400,
RGB(rand()%256,rand()%256,rand()%256);
}
EndPaint(hWnd, &ps);
return 0;
하지만, 이렇게 작성되면 다른 메세지가 발생했을 때 발생한 메세지를 처리할 수 없습니다.
반복문에서 제어권을 독점하고 있기 때문에 올바르지 못한 코드입니다.
제어원을 독점하지 않고, 타이머를 활용해서 백그라운드 작업을 하는 프로그램의 윈도우 프로시저는 다음과 같습니다.
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage,
WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
int i;
switch (iMessage) {
case WM_CREATE:
SetTimer(hWnd, 1, 50, NULL);
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));
}
프로그램이 시작하면 발생하는 메세지인 WM_CREATE 메세지에서 타이머를 50/1000 초 마다 발생시킵니다. 타이머가 발생할 때마다 임의의 좌표에 랜덤 색상을 갖는 점을 찍게 됩니다. 그와 동시에 왼쪽 마우스가 클릭되면 발생하는 메세지인 WM_LBUTTONDOWN 메세지가 발생하면 이를 처리하게 됩니다.
결과

보시는 것처럼 무수한 점이 찍히는 와중에 마우스를 클릭하면 원을 출력하는 모습을 확인할 수 있습니다.
콜백 함수(Callback Function)
일반적인 API함수들은 운영체제가 제공하고, 프로그램은 이 함수를 호출해서 운영체제의 서비스를 받는 형태입니다. 쉽게 말해서, 운영체제가 제공하는 함수를 응용 프로그램이 필요할 때 호출해서 사용하는 것입니다. 개발자가 필요한 기능이 있을 때, 운영체제가 제공하는 함수를 찾아서 호출하는 것입니다. 예를 들어, 위에서 타이머를 생성할 때 사용한 SetTimer() 함수 또한 운영체제가 제공하는 함수이고, 저희가 타이머를 설치하기 위해 호출해서 사용한 것입니다.
반대로, 응용 프로그램이 제공하는 함수 중에 운영체제가 필요한 함수가 있으면 호출하는 것이 콜백 함수(Callback Function)입니다. 말로 보면 낯설게 느껴질 수 있지만, 우리는 여태까지 계속해서 콜백 함수를 사용하고 있었습니다. 바로, 메세지 처리 전용 함수인 윈도우 프로시저(WndProc) 함수입니다. 함수의 선언문을 보면 CALLBACK이라고 명시되어 있습니다. 메세지가 발생하면 운영체제는 해당 함수를 호출해 메세지를 처리합니다. 콜백 함수는 응용 프로그램 내부에 있지만, 응용 프로그램은 함수를 직접 호출하지 않고 오직 운영체제만이 이 함수를 호출합니다. 쉽게 말해서, 윈도우 프로시저 함수는 응용 프로그램 내부에 있지만 저희는 한 번도 호출한 적이 없습니다. WinMain() 함수에서 WndProc()함수를 호출하는 부분은 없습니다. 왜냐하면, 윈도우 프로시저 함수는 콜백 함수이기 때문에 운영체제가 호출하는 것이기 때문입니다.
타이머를 설치하는 함수 SetTimer() 함수의 4번째 매개 변수는 타이머가 발생했을 때 호출할 함수였습니다. 여태까지 NULL을 주면서, WM_TIMER 메세지가 발생하고, wParam에 전달된 타이머의 ID를 통해 메세지를 처리했는데요. 콜백 함수를 작성해서 NULL 대신 해당 함수를 넣어 타이머를 처리하도록 해보겠습니다.
Callback2 프로젝트
이번 프로젝트는 RandGrp 프로젝트(위에서 진행한 프로젝트)에서 타이머를 WM_TIMER 메세지를 처리하는 대신, 타이머를 처리하는 콜백 함수인 TimerProc() 함수를 작성해서 처리하도록 변경하도록 하겠습니다. 타이머가 발생하면 해당 콜백 함수가 호출되어 원을 출력하는 작업을 처리할 것입니다.
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;
PAINTSTRUCT ps;
int i;
switch (iMessage) {
case WM_CREATE:
SetTimer(hWnd, 1, 50, 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));
}
특정 작업(임의의 원을 출력하는)을 처리하는 콜백 함수인 TimerProc()함수를 작성했습니다. 함수 내부에서 동작하는 작업은 기존 WM_TIMER에서 처리하던 내용과 동일합니다. 따로 함수로 빼서 작성했기 때문에 윈도우 프로시저 함수(WndProc)가 간결해졌고, 가독성이 향상된 모습을 볼 수 있습니다.
WM_CREATE 메세지를 처리하는 부분에 존재하는 SetTimer() 함수의 네 번째 매개 변수로 TimerProc() 함수를 넣어주면서 50/1000초마다 타이머가 발생하면 운영체제가 TimerProc() 함수를 호출하도록 작성했습니다.
결과는 위 프로젝트와 같기 때문에 생략하도록 하겠습니다.
정리
백그라운드 작업과 콜백 함수에 대해서 알아보았습니다. 다양한 작업을 동시에 할 수 있다는 것은 엄청난 자유를 얻게된 기분이 듭니다. 다음 포스팅에서는 다양한 작업을 하기 위한 작업영역에 대해서 알아보도록 하겠습니다.
'개발 > Win32 API Programming' 카테고리의 다른 글
[Windows API] Win32 API의 그래픽, GDI와 스톡 오브젝트(Stock Object) (0) | 2022.02.08 |
---|---|
[Windows API] Win32 API를 활용해 작업 영역 얻기 (0) | 2022.01.27 |
[Windows API] Win32 API를 활용해 시계, 일회용 타이머 만들기 - (2) (0) | 2022.01.18 |
[Windows API] Win32 API의 타이머를 활용해 시계 만들기 - (1) (0) | 2022.01.11 |
[Windows API] Win32를 활용해 마우스 입력하기 (0) | 2022.01.09 |