/* 
written by kaspy (kaspyx@gmail.com)
*/ 


 이번 블로그 내용은 윈도우 메시지(Message)에 대해서 다루기로 하겠습니다.-*

기초적인 내용이라기보다는 기본적인 내용에 가깝고, 공부하면서 도움이 될만한 중요한것들만 정리해 보았습니다.


윈도우 애플리케이션은 메시지를 기반으로 모든 처리를 하기 때문에, 메시지는 중요한 요소이다. 그래서 정리를해보았다.


1. 메시지는 단시간에 처리한다.

 윈도우 어플리케이션은 MSG 구조체 형태를 사용하여 메시지를 단시간에 처리한다. 메시지 루프라는 반복 구조를 사용하여 큐에 모인 메시지를 하나씩 꺼내서 순서에 따라 처리하는 방식이라고 보면된다. 그런데 이게 무슨소린고하니, 메시지 큐에 작업이 쌓였는데, 시간이 오래 걸리는 하나의 작업이 큐에 잡혀버리면 뒤에 대기하는 작업들이 실행되지않고 일명 먹통이 되어버린다는 소리이다. 그러니 하나의 메시지 이벤트에 대해서는 가능한한 빨리 처리해주는것이 좋다. 


아래는 전형적인 메시지 처리 루틴에대한 코드이다.


  1. while ( GetMessage(&msg, 0, 0, 0)) {
  2.         DispatchMessage(&msg);
  3. }


메시지 큐에 작업이 들어가서 그작업이 끝나지않고 오래 걸린다면, 그시간동안 다른작업이 먹통이 된다는 의미이다.


2. 장시간을 필요로 하는 처리는 분할해서 실행한다.

 실행 시간이 오래걸리는 작업에 대해서 처리하는 방법은 크게 두가지로 볼수 있다.


 1) 처리를 분할하는 방법

 2) 멀티스레드를 이용하는 방법


이번 블로그에서는 1)번 방법에 대해서 다룰것인데, 아래와 같은 코드가 주어졌다고 해보자.


  1. LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wPram, LPARAM lParam)
  2. {
  3.         switch(uMsg){
  4.         case WM_LBUTTONUP:
  5.                 Sleep(60 * 1000 );      // 60초 정지, 해당 프로시저가 지정된 윈도우는 먹통이된다.
  6.                 break;
  7.         //....
  8. }


이경우 마우스 버튼을 클릭했을경우, 60초동안 윈도에서는 다음 이벤트에 대해서 처리를 하지못하기 때문에 아무작동을 하지않고 "응답없음" 상태에 빠지게 된다. 만약 오랜시간이 걸리는 작업일 경우라면 SetTimer와 KillTimer API 함수를 사용해서 처리할수 있다.

아래는 두함수에 대한 프로토타입이다.


  1. UINT SetTimer(          //반환값: 성공하면 타이머 ID, 실패시 0
  2.         HWND hWnd,      // 타이머를 설정한 윈도우 핸들
  3.         UINT nIDEvent,  // 타이머 ID
  4.         UINT uElapse,   // 타이머 이벤트 시간
  5.         TIMERPROC lpTimerFunc   // 콜백 함수 주소
  6. );
  7.  
  8. BOOL KillTimer(         //반환값: 성공이면 TRUE
  9.         HWND hWnd,      // 타이머가 설정된 윈도우 핸들
  10.         UINT uIDEvent   // 타이머 ID
  11. );


대략 사용법은 작업이 오래 걸리는 함수에서 시작할때 SetTimer함수를 실행하고, 이를 처리하는 윈도우 프로시저 함수에서 WM_TIMER 에대한 이벤트를 처리해주는 방법으로 사용된다.


3. PeekMessage API를 사용해 공전시간에 처리한다.

 타이머를 이용한 방법은 그다지 효율적이지 못하다. 왜냐하면 작업을 하는 시간에 상관없이 항상 타이머 시간동안 아무 작업도 하지 않기 때문이다.

이를 해결하기 위해서 GetMessage 대신 PeekMessage라는 API를 사용한다.


  1. #define WM_FILECOPY     (WM_USER)
  2. //...
  3.  
  4. while(1){
  5.         if ( PeekMessage(&msg,0,0,0, PM_REMOVE) ){
  6.         // 메시지가 있을 경우 메시지를 처리
  7.         if (msg.message == WM_QUIT ) break;
  8.         DispatchMessage(&msg);
  9.         }
  10.         else {
  11.                 if (g_dsn[0] != '\0') {
  12.                         SendMessage(hWnd, WM_FILECOPY, 00);
  13.                 }
  14.         }
  15. }
  16. //...


* GetMessage vs PeekMessage
 GetMessage는 메시지를 꺼내면서 큐에서 메시지를 삭제하는데 반해, PeekMessage는 마지막 인자를 PM_NOREMOVE로 지정하면 큐에서 메시지를 없애지않고 내용을 볼수 있다. 또한 큐가 비어있을때의 행동이다. GetMessage는 메시지를 받아들일 때까지 복귀하지 않지만, PeekMessage는 큐에 메시지가 없는 경우에도 즉석에서 제어를 돌려주게 되어있다.
 따라서 PeekMessage는 메시지가 있는 경우에는 그이벤트를 우선처리하고 메시지가 없는경우에 다른 일을 하도록 만들수 있다. 
 그러므로 파일 복사와같이 오랜시간이 걸리는 작업은 PeekMessage를 사용하여 처리하는것이 좋다.

4. 독자적인 메시지를 정의한다.

메시지는 사실 사용자가 정의해서 보낼수있는데, 바로 위에 소스코드에서처럼 SendMessage로 사용자 정의 메시지를 보내는것이 예로 들수 있다. SendMessage에 대한 프로토 타입은 아래와 같다.


  1. LRESULT SendMessage (   // 반환값: 메시지 처리결과
  2.         HWND hWnd,      // 윈도우 핸들
  3.         UINT uMsg,      // 메시지 id
  4.         WPARAM wParam,          // 메시지의 첫번째 전달인자
  5.         LPARAM lParam           // 메시지의 두번째 전달인자
  6. );


두번째 인자 uMsg 메시지 ID는 32비트 양의 정수로 식별되며, WM_으로 시작하여 정의 되어있다.


참고로 윈도우 메시지는 winuser.h 헤더파일에 정의되어있다. (엄청많음!!)



참고로 사용자 메시지 정의를 하기위해선 아래와 같은 방법을 추천한다. 이방법은 시스템 내에서 유일성이 보증되는 방법으로 여러개의 프로그램들 사이에서 독자 메시지를 서로 호출할수 있다.


  1. #define WM_MESSAGE1     (WM_USER+1)
  2. #define WM_MESSAGE2     (WM_USER+2)
  3. //....


아니면 RegisterWindowMessage API를 호출하여 메시지를 정의해줄수 있다.


  1. UINT RegisterWindowMessage(     //반환값 : 등록된 메시지 ID
  2.         LPCTSTR lpString        //등록할 문자열
  3. );


5. PostMessage를 사용해 큐를 경유하는 메시지를 보낸다.

 SendMessage를 보내서 메시지를 보낼수 있지만 PostMessage을 사용해서 메시지를 보낼수도 있다.


  1. BOOL PostMessage (      //반환값: 호출 성공 여부
  2.         HWND hWnd,      // 윈도우 핸들
  3.         UINT uMsg,      // 메시지 ID
  4.         WPARAM wParam,  // 메시지의 첫번째 전달인자
  5.         LPARAM lParam   // 메시지의 두번째 전달인자
  6. );


* SendMessage vs SendMessage

SendMessage와 PostMessage 차이점은 SendMessage는 메시지가 처리가 끝날때까지 기다리지만 PostMessage는 메시지만 보낸후 처리가 끝가기를 기다지리 않고 바로 복귀한다는점이다.


6. 메시지 전달인자에 포인터를 넘긴다.

 사용자 메시지를 자율적으로 보낼수 있는데, 복잡한 전달 인자인 경우는 WPARAM과 LPARAM인자를 활용할수있다. 이경우에는 포인터를 사용하여 활용할수있다. 단 주의점은 SendMessage를 사용해야 한다는점.. 포인터를 통해 메모리등을 할당한후에 해제등을 반드시 해주거나, 지역변수등에 잡힌 포인터등은 그함수내에서만 유효하기 때문에 명확히 해줘야할 필요가 있기 때문이다.


* 참고자료 및 서적

API로 배우는 Windows 구조와 원리

C언어 윈도우즈 API 프로그래밍


* 링크 참조


윈도우(Window)의 구성요소(Style) 및 종류

윈도우(Windows) 애플리케이션(application)의 구조


Posted by 캐스피

댓글을 달아 주세요

  1. ㅇㅇ 2019.02.15 11:57  댓글주소  수정/삭제  댓글쓰기

    잘보고갑니다