카테고리 없음

GDI 프로그램의 기본 구조 I : WM_PAINT 메시지 처리 중심

정현석 2010. 4. 14. 09:42
Win32 프로그램은 그래픽 기반의 프로그램이다. 그래픽 기반의 프로그램이라 하면 일단 그림을 그릴 수 있는 윈도우가 필요하다. 이 윈도우 위에 선으로 그림을 그리고, 붓으로 색을 칠하고, 화면에 글자를 표현하는 방식의 프로그램이 그래픽 기반의 프로그램이다. 이러한 프로그램은 너무도 시각적이기 때문에 일반 사용자들에게 친숙한 느낌을 줄 수 있다는 장점이 있는 반면 프로그래머가 해야 할 일은 생각보다 많아진다. 이번 호에서는 WM_PAINT 메시지의 동작 원리를 살펴보고, 어떻게 응용할 수 있는지도 알아보자.

서민우 http://www.we dusolution.com|Xilinx 국내 공식 인증 교육전문기관에서 FPGA 및 Embedded SOC 강의 진행 및 개발을 하고 있다. 또 비트교육센터에서 Win32, C++, Embedded Linux 등을 강의하고 있다.

WM_PAINT 메시지는 윈도우 상에 그림을 그리는데 있어 핵심적인 역할을 한다. 이 메시지는 그동안 봐왔던 메시지들에 비해 원리가 무척 복잡하다. 하지만 윈도우 프로그램을 자유롭게 하기 위해 WM_PAINT 메시지는 반드시 넘어야 할 산이다.

다음의 내용을 살펴보며 메시지를 이해해 보자.

1. WM_PAINT 메시지는 언제 발생하는가? 
2. WM_PAINT 메시지는 어떻게 처리해야 하는가?

WM_PAINT 메시지는 두 가지 이유로 발생한다.  

첫 째, 마우스나 키보드 등 외부 입력장치에 의해 윈도우 클라이언트 영역의 가려진 부분이 보이게 될 때 OS에 의해 발생한다. 이는 하드웨어에 의해 발생한 경우이다.

둘 째, InvalidateRect나 InvalidateRgn과 같은 함수를 호출해 사용자의 코드 영역에서 의도적으로 발생시킨다. 이는 소프트웨어에 의해 발생한 경우이다.

이 두 가지 모두 클라이언트 영역에 새롭게 그려야 할 그림 등이 있다는 것을 의미한다.

그럼 WM_PAINT 메시지의 동작을 구체적으로 살펴보자. 
 
WM_PAINT 메시지 미처리
먼저 <리스트 1>과 같이 예제를 작성해 보자.

<리스트 1> WM_PAINT 발생 시 메시지 미처리 예

#include <windows.h>
#include <stdio.h>
 
LRESULT CALLBACK MyWndProc(HWND,
   UINT,
   WPARAM,
   LPARAM);
 
int main()

   WNDCLASS wc = {0,};
   wc.lpszClassName = "MyWnd";
   wc.lpfnWndProc = MyWndProc;
   wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
   wc.hCursor = LoadCursor(0,IDC_ARROW);
   wc.hIcon = LoadIcon(0,IDI_APPLICATION);
   RegisterClass(&wc);
 
   HWND hwnd = CreateWindow("MyWnd","Hellow Window",
      WS_OVERLAPPEDWINDOW|WS_VISIBLE,
      100,100,200,100,0,0,0,0);
             
   MSG msg;
   while(GetMessage(&msg,0,0,0)) {
      TranslateMessage(&msg);
      DispatchMessage(&msg);
   }
   return msg.wParam;
}
 
LRESULT CALLBACK MyWndProc(HWND hWnd,
   UINT msg,                                              
   WPARAM wParam,
   LPARAM lParam) 
{
   if(msg == WM_DESTROY) {
      PostQuitMessage(0);
      return 0;
   } elseif(msg==WM_PAINT){                    
   printf(".");
   /* 
    그냥 리턴할 경우 계속해서 WM_PAINT 메시지가 발생한다.
   */
   return 0;
   }
   return DefWindowProc(hWnd,msg,wParam,lParam);
}

<리스트 1>에서는 WM_PAINT 메시지 발생 시 메시지에 대한 처리 없이 printf(“.”)함수만 수행하고 그냥 return 했다.

컴파일 한 후 프로그램을 실행시켜 보자(<화면 1> 참조). 


<화면 1> <리스트 1>의 실행 화면

<화면 1>에서 WM_PAINT 메시지가 끊임없이 발생하는 것을 볼 수 있다.
 
WM_PAINT 메시지 처리
WM_PAINT 메시지의 경우 OS에서 제공하는 함수를 이용해 메시지 처리에 대한 완료 여부를 OS에게 알려줘야 한다. 그렇지 않으면 WM_PAINT 메시지는 메시지 큐에 계속 남아있게 되며, OS는 메시지가 처리될 때까지 계속해서 발생시킨다.

WM_PAINT 메시지가 발생할 경우 BeginPaint() 또는 ValidateRect() 함수 둘 중 하나를 반드시 호출해야 하는데, 이들 함수는 WM_PAINT 메시지 처리를 위해 OS에서 제공하는 것이다.

BeginPaint() 함수의 경우 새로 그려야 할 영역을 먼저 지우는 기능이 있으며, EndPaint() 함수와 함께 반드시 짝을 이뤄 사용한다.

ValidateRect() 함수의 경우는 GetUpdateRect() 함수를 통해 새로 그려야 할 영역을 먼저 얻어내야 한다. 즉, 다음과 같은 순서로 사용해야 한다.

GetUpdateRect(...); 
ValidateRect(...);

GetUpdateRect 함수도 BeginPaint() 함수와 같이 새로 그려야 할 영역을 먼저 지우는 기능을 한다.

결론적으로 WM_PAINT 메시지가 연속해서 발생하는 것을 막기 위해 <리스트 2>나 <리스트 3>과 같은 루틴을 쓸 수 있다.

<리스트 2> BeginPaint() 함수 사용 시

if(msg == WM_PAINT) {
   PAINTSTRUCT ps;           
   HDC hdc;
   hdc = BeginPaint(hWnd,&ps);
   EndPaint(hWnd,&ps);
   return 0;
}

<리스트 3> ValidateRect() 함수 사용 시

if(msg == WM_PAINT) {                 
   RECT rc;
   GetUpdateRect(hWnd,&rc,TRUE);
   ValidateRect(hWnd,&rc);  
   return 0;
}

여기서 <리스트 2>와 <리스트 3>은 단순히 WM_PAINT 메시지가 무한히 반복되는 것을 막기 위한 루틴만 추가한 것이다. 그러니 <리스트 2>와 <리스트 3>에서 그림을 그리는 루틴이 없다 하더라도 코드상의 문제는 없다.

<리스트 1>에서 WM_PAINT 메시지를 처리하는 부분을 <리스트 4>와 같이 바꿔 보자. 
 

<리스트 4> BeginPaint() 함수를 통한 WM_PAINT 메시지 처리

LRESULT CALLBACK MyWndProc(HWND hWnd,
   UINT msg,                                              
   WPARAM wParam,
   LPARAM lParam) 
{
   if(msg == WM_DESTROY) {
      PostQuitMessage(0);
      return 0;
   }elseif(msg==WM_PAINT){
      PAINTSTRUCT ps;     
      HDC hdc;
      hdc = BeginPaint(hWnd,&ps);
      printf(".");
      EndPaint(hWnd,&ps);
      return 0;
   }
   return DefWindowProc(hWnd,msg,wParam,lParam);
}

<리스트 4>에서 PAINTSTRUCT 구조체와 HDC 구조체를 볼 수 있다. BeginPaint() 함수는 PAINTSTRUCT 구조체를 통해 그려야 할 영역에 대한 정보를 얻어온다(이 구조체에 대해서는 뒤에서 자세히 살펴보자). HDC 구조체는 디바이스 컨텍스트 핸들이라 불리며, 윈도우 프로그램에서는 디바이스 컨텍스트에 대한 핸들을 얻어야 화면에 그림을 그릴 수가 있다.

DC에 대한 사용 예도 뒤에서 자세히 알아보자.

컴파일 한 후 프로그램을 실행시키면 <화면 2>와 같은 화면을 볼 수 있다.


<화면 2> <리스트 4>의 실행 화면

<화면 2>는 WM_PAINT 메시지가 한 번 처리 된 것을 보여준다.

ValidateRect() 함수와 GetUpdateRect() 함수를 통해서도 WM_PAINT 메시지를 처리해 보자.

<리스트 1> 에서 WM_PAINT 메시지를 처리하는 부분을 <리스트 5>와 같이 바꿔보자.

<리스트 5> ValidateRect() 함수를 통한 WM_PAINT 메시지 처리

LRESULT CALLBACK MyWndProc(HWND hWnd,
   UINT msg,                                              
   WPARAM wParam,
   LPARAM lParam) 
{
   if(msg == WM_DESTROY) {
      PostQuitMessage(0);
      return 0;
   }elseif(msg==WM_PAINT){
      RECT rc;
      GetUpdateRect(hWnd,&rc,TRUE);
      ValidateRect(hWnd,&rc);       
      printf(".");
      return 0;
   }
   return DefWindowProc(hWnd,msg,wParam,lParam);
}

<리스트 5>에서는 RECT 구조체를 볼 수 있다. GetUpdate Rect() 함수는 RECT 구조체를 통해 새로 그려야 할 클라이언트 영역의 위치와 크기를 얻어 오며, ValidateRect() 함수는 OS에게 이 영역에 대한 처리를 하겠다고 알린다.

컴파일 한 후 프로그램을 실행시켜 보자(<화면 3> 참조).


<화면 3> <리스트 5>의 실행 화면

<화면 3>에서도 WM_PAINT 메시지가 한 번 처리 된 것을 볼 수 있다.

이로써 우리는 <리스트 4>, <리스트 5>에서 WM_PAINT 메시지를 처리하는 중요한 두 가지 함수를 살펴봤다. BeginPaint() 함수와 ValidateRect() 함수는 절대 잊지 않도록 하자. 
 
그림은 어디서 그려야 하는가
WM_PAINT 메시지 처리 루틴에서 그림을 그리고자 할 경우에는 <리스트 6>이나 <리스트7>과 같이 할 수 있다.

<리스트 6> BeginPaint() 함수 사용 시 그림 그리는 위치

if(msg == WM_PAINT) {
   PAINTSTRUCT ps;           
   HDC hdc;
   hdc = BeginPaint(hWnd,&ps);
   // GDI 함수를 이용해 그림 또는 글씨를 그린다.
   EndPaint(hWnd,&ps);
   return 0;
}

<리스트 7> ValidateRect() 함수 사용 시 그림 그리는 위치

if(msg == WM_PAINT) {                 
   RECT rc;
   GetUpdateRect(hWnd,&rc,TRUE);
   ValidateRect(hWnd,&rc);  
   HDC hdc = GetDC(hWnd);
   // GDI 함수를 이용해 그림 또는 글씨를 그린다.
   ReleaseDC(hWnd,hdc);
   return 0;
}


 
<리스트 7>에서는 GetDC() 함수를 새롭게 볼 수 있다. 이 함수는 클라이언트 영역에 그림을 그리기 위해 HDC를 얻어오는 역할을 하며, 꼭 기억해야 한다. GetDC() 함수는 반드시 Release DC() 함수와 짝을 이뤄야 한다는 것도 알아두자.

<리스트 6>과 <리스트 7>의 경우 모두 윈도우의 클라이언트 영역에 그림을 그릴 수 있다. 엄밀히 말하면 <리스트 6>과 <리스트 7>에서 BeginPaint() 함수와 GetDC() 함수를 통해 얻는 HDC의 성격은 다르다. BeginPaint 함수를 통해 얻는 HDC로는 새로 그려야 할 영역(무효화된 영역이라고 한다)에만 그림을 그릴 수 있으나 GetDC 함수를 통해 얻는 HDC로는 클라이언트 영역 전체에 그림을 그릴 수 있는 것이다. 이 두 루틴의 차이점은 다음 호에서 자세히 살펴볼 것이다.

그럼 <리스트 8>과 <리스트 9>를 통해 그림을 그려 보자.

먼저 <리스트 1>에서의 WM_PAINT 메시지 처리 부분을 <리스트 8>과 같이 바꿔보자.

<리스트 8> BeginPaint() 함수를 통한 그림 그리기

LRESULT CALLBACK MyWndProc(HWND hWnd,
   UINT msg,                                              
   WPARAM wParam,
   LPARAM lParam) 
{
   if(msg == WM_DESTROY) {
      PostQuitMessage(0);
      return 0;
   }elseif(msg==WM_PAINT){
   PAINTSTRUCT ps;     
   HDC hdc;
   hdc = BeginPaint(hWnd,&ps);
   printf(".");
   // GDI 함수를 이용해 그림 또는 글씨를 그린다.
   Rectangle(hdc,20,20,40,40);
   Rectangle(hdc,120,20,140,40);         
   EndPaint(hWnd,&ps);
   return 0;
   }
   return DefWindowProc(hWnd,msg,wParam,lParam);
}

<리스트 8>에서 Rectangle() 함수는 윈도우의 클라이언트 영역에 직사각형을 하나 그린다. 이 함수의 첫 번째 인자로 HDC 구조체 데이터가 사용되는 것을 볼 수 있다. 이 HDC 구조체는 일반적으로 BeginPaint() 함수나 GetDC() 함수를 통해 얻어올 수 있다. 

Rectangle() 함수의 타입은 다음과 같다.

BOOL Rectangle(
   __in  HDC hdc,
   __in  int nLeftRect,
   __in  int nTopRect,
   __in  int nRightRect,
    __in  int nBottomRect
);

● hdc [in] 
디바이스 컨텍스트 핸들
 
● nLeftRect [in] 
X좌표로 그리고자 하는 사각형의 상단 왼쪽 좌표를 나타낸다.
 
● nTopRect [in] 
Y좌표로 그리고자 하는 사각형의 상단 왼쪽 좌표를 나타낸다.
 
● nRightRect [in] 
X좌표로 그리고자 하는 사각형의 하단 오른쪽 좌표를 나타낸다.
 
● nBottomRect [in] 
Y좌표로 그리고자 하는 사각형의 하당 오른쪽 좌표를 나타낸다.
 
Rectangle() 함수의 두 번째에서 네 번째 인자는 각각 값을 바꿔가며 프로그램을 실행해 보면 그 역할을 쉽게 이해할 수 있을 것이다.

그럼 <리스트 8>을 컴파일해 실행시켜 보자. <화면 4>는 <리스트 8>의 실행화면이다.

<화면 4>에 두 개의 사각형이 그려진 것을 볼 수 있다.


<화면 4> <리스트 8>의 실행 화면

다음은 <리스트 1>에서의 WM_PAINT 메시지 처리 부분을 <리스트 9>와 같이 바꿔보자.

<리스트 9> GetDC() 함수를 통한 그림 그리기

LRESULT CALLBACK MyWndProc(HWND hWnd,
   UINT msg,                                              
   WPARAM wParam,
   LPARAM lParam) 
{
   if(msg == WM_DESTROY) {
      PostQuitMessage(0);
      return 0;
   }elseif(msg==WM_PAINT){
      RECT rc;
      GetUpdateRect(hWnd,&rc,TRUE);
      ValidateRect(hWnd,&rc);
      printf(".");
      HDC hdc = GetDC(hWnd);
      // GDI 함수를 이용해 그림 또는 글씨를 그린다.
      Rectangle(hdc,20,20,40,40);
      Rectangle(hdc,120,20,140,40);
      ReleaseDC(hWnd,hdc);                     
      return 0;
   }
   return DefWindowProc(hWnd,msg,wParam,lParam);
}

<리스트 9>를 컴파일해 실행하면 <화면 5>와 같다.

<화면 5>에서도 <화면 4>와 같이 두 개의 사각형이 그려진 것을 볼 수 있다.


<화면 5> <리스트 9>의 실행 화면

이상에서 우리는 <리스트 8>, <리스트 9>를 통해 WM_PAINT 메시지 처리 루틴 내에서 그림을 그릴 수 있는 위치에 대해 살펴봤다. 이 역시 중요한 부분이니 잊지 않도록 하자. 
다음으로 넘어가기 전에 한 가지 살펴볼 것이 있다. <리스트 10>을 보자. 

<리스트 10> ValidateRect() 함수와 BeginPaint() 함수의 혼용 예

if(msg == WM_PAINT) {      
   RECT rc;
   GetUpdateRect(hWnd,&rc,TRUE);
   ValidateRect(hWnd,&rc);
   PAINTSTRUCT ps;           
   HDC hdc;
   hdc = BeginPaint(hWnd,&ps);
   // GDI 함수를 이용해 그림 또는 글씨를 그린다.
   EndPaint(hWnd,&ps);
   return 0;
}

<리스트 10>과 같이 WM_PAINT 메시지를 처리할 경우 그림을 그릴 수 있을까? 일단 결론은 그림을 그릴 수 없다는 것이다.

<리스트 10>의 경우는 GetUpdateRect() 함수를 이용해 무효화된 영역(그림을 그려야 할 영역)의 정보를 얻은 후 Validate Rect() 함수를 통해 이 영역을 유효화(OS에게 WM_PAINT 메시지를 처리하겠다고 알려주는 역할을 하며 WM_PAINT 메시지의 추가발생을 막는다)한다.

하지만 이렇게 할 경우 BeginPaint() 함수를 통해 얻은 DC를 이용해서는 클라이언트 영역에 새로운 그림이나 글씨를 그릴 수 없다. 왜냐하면 BeginPaint() 함수는 무효화된 영역에만 그림을 그릴 수 있는데, 이 영역이 이미 ValidateRect() 함수를 통해 유효화 됐기 때문이다. 그래서 이럴 경우에는 GetDC() 함수를 사용해야 한다. 즉, <리스트 9>와 같은 형태로만 그릴 수 있는 것이다.

<리스트 10>의 내용을 <리스트 11>을 통해 테스트 해 보자.

<리스트 1>에서 WM_PAINT 메시지를 처리하는 부분을 <리스트 11>과 같이 바꾼다.

<리스트 11> GetUpdateRect() 함수와 BeginPaint() 함수의 만남

LRESULT CALLBACK MyWndProc(HWND hWnd,
   UINT msg,                                              
   WPARAM wParam,
   LPARAM lParam) 
{
   if(msg == WM_DESTROY) {
      PostQuitMessage(0);
      return 0;
   } elseif(msg==WM_PAINT){           
      RECT rc;
      GetUpdateRect(hWnd,&rc,TRUE);
      ValidateRect(hWnd,&rc);
      PAINTSTRUCT ps;     
      HDC hdc;
      hdc = BeginPaint(hWnd,&ps);
      // GDI 함수를 이용해 그림 또는 글씨를 그린다.
      Rectangle(hdc,20,20,40,40);
      Rectangle(hdc,120,20,140,40);
      EndPaint(hWnd,&ps);
      return 0;
   }
   return DefWindowProc(hWnd,msg,wParam,lParam);
}

<리스트 11>을 컴파일하고 실행시켜 보자. <화면 6>이 <리스트 11>의 실행화면이다.


<화면 6> <리스트 11>의 실행 화면

<화면 6>에서는 두 개의 사각형이 보이지 않는다.

즉, BeginPaint() 함수는 ValidateRect() 함수에 의해 이미 유효화된 영역에는 그림을 그릴 수 없다는 것을 보여준다.

PAINTSTRUCT 구조체 정보
이제 WM_PAINT 메시지 발생시 BeginPaint() 함수와 GetUpdateRect() 함수를 통해 새로 그려야 할 영역에 대한 정보가 어떻게 넘어오는지 살펴보자.

앞에서 필자는 WM_PAINT 메시지가 발생하는 이유로 두 가지가 있다고 했다.

첫 번째로 하드웨어에 의해 발생하고, 두 번째로 소프트웨어에 의해 발생한다고 했다.

이 중 먼저 하드웨어에 의한 WM_PAINT 메시지를 발생시켜 보고 새로 그려야 할 영역에 대한 정보가 어떻게 넘어오는지 <리스트 12>과 <리스트 13>를 통해 살펴보자.

<리스트 1>에서 WM_PAINT 메시지를 처리하는 부분을 <리스트 12>와 같이 바꿔보자.

<리스트 12> PAINTSTRUCT 구조체의 내용

LRESULT CALLBACK MyWndProc(HWND hWnd,
   UINT msg,                                              
   WPARAM wParam,
   LPARAM lParam) 
{
   if(msg == WM_DESTROY) {
      PostQuitMessage(0);
      return 0;
   }elseif(msg==WM_PAINT){
      PAINTSTRUCT ps;     
      HDC hdc;
      hdc = BeginPaint(hWnd,&ps);
      printf("(%d,%d) - (%d,%d)\n",
      ps.rcPaint.left, 
      ps.rcPaint.top,
      ps.rcPaint.right, 
      ps.rcPaint.bottom);    
      EndPaint(hWnd,&ps);
      return 0;
   }
   return DefWindowProc(hWnd,msg,wParam,lParam);
}

<리스트 12>에서 BeginPaint() 함수는 PAINTSTRUCT 구조체를 통해 새로 그려야 할 영역에 대한 정보를 얻어온다. <리스트 12>를 컴파일 한 후 실행시켜 보자. <화면 7>이 그 실행결과이다.

<화면 7>에서는 클라이언트 영역 전체에 대한 좌표 영역 정보가 넘어온다. 최초에 발생하는 WM_PAINT 메시지의 경우는 클라이언트 영역 전체에 대한 좌표 영역을 넘겨주게 된다. 따라서 전체 영역에 대한 그림을 그릴 수 있다.

<화면 7>에서 하나 이상의 작은 윈도우(예를 들어, 두 개의 작은 윈도우)로 클라이언트 영역을 가렸다가 드러나게 할 경우 새로 그려야 할 영역을 update region이라고 한다. 그리고 PAINTSTRUCT 구조체를 통해 새로 그려져야 할 모든 영역(예를 들어, 작은 윈도우에 가려졌던 두 영역)을 둘러싸는 가장 작은 크기의 사각형의 영역 정보를 받는 것도 볼 수 있다.


<화면 7> <리스트 12>의 실행 화면

하드웨어에 의한 WM_PAINT 메시지 발생 원리
다음과 같이 하드웨어적으로 WM_PAINT 메시지를 발생시켜 보자. 

1. 윈도우를 최소화했다 원래대로 해 본다.
2. 윈도우를 최대화했다 원래대로 해 본다.
3. 윈도우 창을 늘였다 줄였다 해 본다.

<화면 8>은 윈도우를 최소화했다 원래대로 하는 경우의 결과화면이다.


<화면 8> 윈도우를 최소화했다 원래대로 하는 경우

<화면 9>는 윈도우를 최대화했다 원래대로 하는 경우이다.


<화면 9> 윈도우를 최대화했다 원래대로 하는 경우

<화면 10>은 윈도우를 늘렸다 줄였다 할 경우의 결과화면이다.


<화면 10> 윈도우를 늘였다 줄였다 하는 경우


 필자의 테스트 환경은 윈도우 7이다. 윈도우 XP의 경우 앞의 방법에 다음과 같은 방법도 추가해 보자. 

4. 다른 윈도우로 전체를 가렸다 보였다 해 본다.
5. 다른 윈도우를 테스트 윈도우 위에서 끌어본다.

RECT 구조체 정보
<리스트 13>에서 GetUpdateRect() 함수를 통해 넘어오는 정보를 살펴보자. <리스트 1> 에서 WM_PAINT 메시지를 처리하는 부분을 <리스트 13>와 같이 바꾼다.

<리스트 13> RECT 구조체의 내용

LRESULT CALLBACK MyWndProc(HWND hWnd,
   UINT msg,                                              
   WPARAM wParam,
   LPARAM lParam) 
{
   if(msg == WM_DESTROY) {
      PostQuitMessage(0);
      return 0;
   }elseif(msg==WM_PAINT){
      RECT rc;
      GetUpdateRect(hWnd,&rc,TRUE);
      printf("(%d,%d) - (%d,%d)\n",
      rc.left,
      rc.top,
      rc.right,
      rc.bottom);
      ValidateRect(hWnd,&rc);       
      return 0;
   }
   return DefWindowProc(hWnd,msg,wParam,lParam);
}

<리스트 13>에서 새로 그려야 할 영역에 대한 정보가 RECT 구조체를 통해 넘어오는 것을 볼 수 있다. <리스트 13>을 컴파일 한 후 실행하면 <리스트 12>와 같은 것을 알 수 있다.

<리스트 12>와 <리스트 13>에서는 PAINTSTRUCT 구조체와 RECT 구조체의 역할을 살펴 봤다. 또한 하드웨어적으로 WM_PAINT 메시지가 어떻게 발생하는지도 살펴봤다.

이렇게 이번 호에서는 WM_PAINT 메시지가 하드웨어적으로 어떻게 발생하는지 알아봤다. 다음 호에서는 WM_PAINT 메시지가 소프트웨어적으로 어떻게 발생되는지 알아보자. 

개발 팁

사용자 윈도우 프로시저는 윈도우의 동작을 수행하는 부분이다. 프로그램을 시작해 초기화가 끝난 후 윈도우 프로시저는 메시지 루프 내에서 필요할 때마다(메시지 큐에 메시지가 도착해 GetMessage() 함수에 의해 메시지가 꺼내 질 때마다) DispatchMessage() 함수에 의해서 호출되어 수행된다. 프로그램이 동작하는 시간 대부분을 이 함수에서 사용한다고 할 수 있겠다. 물론 프로그램이 기다리는 대부분의 시간은 GetMessage() 함수 내에서 사용된다고 볼 수 있다.

DispatchMessage() 함수 말고도 사용자 윈도우 프로시저를 직접 호출하는 함수들이 있다. 바로 윈도우 생성 시 호출하는 CreateWindow() 함수와 메시지 루프로 들어가기 전에 호출하는 UpdateWindow() 함수이다.

UpdateWindow() 함수의 경우 윈도우 프로시저 내에서도 InvalidateRect() 함수와 함께 사용되는 어려운 함수 중 하나이다(InvalidateRect() 함수와 UpdateWindow() 함수와의 상호 관련성은 다음 호에서 자세히 살펴 볼 것이다).

DispatchMessage()의 경우 대부분의 메시지를 윈도우 프로시저로 보내는 역할을 한다. <그림 1>을 보자.


<그림 1> DispatchMessage()의 역할

마찬가지로 CreateWindow() 함수도 WM_CREATE 메시지를 윈도우 프로시저로 보내며 직접 호출한다. 이 경우 WM_CREATE 메시지는 메시지 큐를 거치지 않고 윈도우 프로시저에 직접 전달되는 것이다. 메시지 큐를 거친다는 것은 GetMessage() 함수를 거쳐 DispatchMessage() 함수에 메시지가 간접적으로 전달된다는 의미이기도 하다.

UpdateWindow() 함수는 프로그램 초기화 과정에 사용되지만 윈도우 프로시저 내에서도 사용되는 것을 볼 수 있다. 호출되는 곳이 여러 군데이다 보니 그 역할을 정확히 이해해야 쓸 수 있는 함수이기도 하다. UpdateWindow() 함수는 WM_PAINT 메시지를 윈도우 프로시저에게 직접 호출하는 역할을 한다. 즉, WM_PAINT 메시지를 처리해 윈도우의 사용자 영역을 새로이 그려야 할 필요성이 있을 때 UpdateWindow() 함수가 사용된다.

Win32 프로그램은 마이크로소프트에서 프로그램의 흐름을 어느 정도 고정시켜 놓았으며, 프로그래머들은 이 흐름 내에서 개발할 수 밖에 없다. 따라서 이러한 흐름을 이해하는 것은 좋은 프로그램을 만들기 위해 꼭 필요한 과정이다. 특히 사용자 윈도우 프로시저가 호출되는 부분이 어디인지, 어느 시점에서 호출되는지를 아는 것은 더욱 중요하다. 이에 우리도 윈도우 프로시저가 호출되는 부분들을 살펴봤다.