'GetMessage'에 해당되는 글 2건

  1. 2015.04.19 윈도우 메시지(Message)
  2. 2015.04.09 윈도우(Windows) 애플리케이션(application)의 구조
/* 
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 캐스피
/* 
written by kaspy (kaspyx@gmail.com)
*/ 


Windows 프로그래밍을 해본 경험이 있는 사람이라면 API 및 Win32 API를 어떤 형태로든 접해본적이 있을 것이다.

API란 Application Programming Interface라는 약어로 윈도 운영체제의 기능을 애플리케이션에서 이용하기 위한 인터페이스로, 그 실체는 C/C++ 언어로 개발한 수천개의 함수의 집합이다.

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


1. Windows 애플리케이션은 이벤트 반응형이다

 Windows의 GUI 애플리케이션을 시작하면 윈도를 출력할 뿐이며, 일반적으로 아무것도 하지 않는다. 메뉴를 선택하거나 마우스 버튼을 클릭하거나 키를 누르는 등의 사용자 조작에 따라 처리해 준다. 이처럼 사용자 조작으로 이벤트가 발생하고, 그때마다 대응되는 이벤트에 처리를 하는 프로그램을 '이벤트 반응형'이라고 한다.

 이벤트 반응형 프로그램이 동작 하기 위해선 이벤트가 발생했음을 알려주는 구조가 필요하다.Windows 에서는 운영체제인 Windows 자신이 장치를 항상 감시하다가 필요에따라 애플리케이션에 통지한다.

 Windows 에서는 감지한 이벤트의 상당수는 특정 애플리케이션을 향해 통지된다. 자체 내부적으로 Windows 메시지 대기열 큐에서 발생한 메시지를 순차적으로 각각의 애플리케이션에 통지하고 각각의 애플리케이션은 메시지를 처리하는 방식을 사용하고있다. 이런 대기행렬을 시스템 큐라고한다.

2. 메시지를 사용해서 이벤트를 통지한다

 Windows 에서 각각의 애플리케이션 윈도우에 이벤트를 통지하려면 '윈도우 메시지(이하 메시지)' 라고 하는 데이터 구조체를 사용하며 그 실체는 아래와 같이 정의 되어있다.

  1. typedef struct tagMSG{
  2.         HWND hwnd;      // 윈도우 핸들
  3.         UINT message;   // 메시지 ID
  4.         WPARAM wParam;  // 메시지 전달 인자 1
  5.         LPARAM lParam;  // 메시지 전달 인자 2
  6.         DWORD time;     // 이벤트 발생 시각
  7.         POINT pt;       // 이벤트 발생시 커서위치
  8. } MSG, *PMSG;

 첫번째 hwnd 라는 멤버는 메지지의 행선지(받을 윈도우)를 식별하기 위한 정수값이다. 애플래케이션이 여러개 띄워져 있는경우 어떤 윈도우인지를 알기위한 식별자인 셈이다.

 두번째, message라는 멤버는 메시지의 종류를 나타내는 ID이다. 일반적인 메시지는 winuser.h 안에 'WM_' 으로 시작하는 심볼형태로 수백개가 정의되어있다. 발생한 이벤트의 종류를 알아내려면 이 값을 보면된다.

 세번째, 네번째 wParam, lParam은 message에 대한 추가적인 정보인데, 메시지의 종류에따라 의미가 다르다. 예를들어 마우스의 왼쪽 버튼이 눌렸을때 받는 메시지(WM_LBUTTONDOWN)에서는 마우스 위치와 Shift 키나 Alt 키 등의 상태가 담겨오고, 키보드가 눌렸을때의 메시지(WM_KEYDOWN)일때는 쿨린 키의 종류를 나타내는 값이 저장되어진다.

 그리고 나머지 2개 time과 pt는 각각 이벤트가 발생했을 때의 시각과 그 순간의 마우스 커서위치를 나타낸다.

3. 윈도우 프로시저를 통해 메시지를 처리한다

 메시지 큐에서 꺼내온 메시지는 그 행선지가 되는 윈도우에 전송되어 거기서 처리된다. 메시지의 행선지가 윈도우 이므로 행선지 윈도우 안에서 처리해야 하는 것이다. 실제 Windows에서는 윈도우에 관련된 특별 함수인 '윈도우 프로시저'에서 메시지를 처리한다

 윈도우 프로시저의 프로토타입은 아래와 같다.

  1. LRESULT CALLBACK WindowProc (
  2.         HWND hwnd,      // 윈도우 핸들
  3.         UINT uMsg,      // 메시지 ID
  4.         WPARAM wParam,  // 메시지 전달인자 1
  5.         LPARAM lParam   // 메시지 전달인자 2
  6. );

 여기서 윈도우 프로시저는 각 윈도우에 연관된 함수인데, 왜 윈도를 식별하는 핸들이 인자로 필요한 것일지 궁금해할지도 모르겠다. 이는 윈도우 프로시저에서 담당하는 윈도우가 반드시 하나라고만 단정할수 없기 때문이다. 예를들어, 애플리케이션에서 동일한 행동을 하는 윈도우 2개를 만들수도 있다. ex 한글 및 엑셀 같은 애플리케이션은 하나의 윈도 안에 자식 윈도가 여러개있다.

 이때문에 Windows에서는 각각의 윈도우와는 별도로 '윈도우 클래스'를 제공한다. 윈도우 클래스는 윈도우 프로시저를 포함해서 일부 공통된 성질을 그룹화 한것이다. 즉 윈도우 클래스는 직접 등록하는것이 일반적이고, 애플리케이션은 메지시 큐에서 메시지를 하나씩 꺼내서 윈도우 프로시저에 전송한다. 그리고 윈도우 프로시저에서는 이벤트에 대응하는 처리가 수행된다.

4. WinMain 함수에서 실행을 시작한다

 아래는 Windows 애플리케이션의 전체 소스코드로, 실행하면 회색 윈도 하나를 출력한다. 윈도 애플리케이션이라면 아래와 같은 구조를 따른다고 보면 되겠다.

  1. #include <Windows.h>
  2.  
  3. // 윈도우 클래스명
  4. #define MYWNDCLSNAME "MyWindowClass"
  5.  
  6. LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  7.  
  8. int WINAPI WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow)
  9. {
  10.         WNDCLASS wndcls;
  11.         HWND hWnd;
  12.         MSG msg;
  13.  
  14.         // 윈도우 클래스 등록
  15.         ZeroMemory(&wndcls,sizeof(wndcls));
  16.         wndcls.lpfnWndProc = WndProc;
  17.         wndcls.hInstance = hInst;
  18.         wndcls.hIcon = LoadIcon(0,IDI_APPLICATION);
  19.         wndcls.hCursor = LoadCursor(0, IDC_ARROW);
  20.         wndcls.hbrBackground = (HBRUSH) COLOR_BACKGROUND;
  21.         wndcls.lpszClassName = MYWNDCLSNAME;
  22.         if (RegisterClass(&wndcls) == 0) return -1;
  23.  
  24.         // 메인 윈도우 생성
  25.         hWnd = CreateWindow(MYWNDCLSNAME, "My WIndow",
  26.                 WS_OVERLAPPEDWINDOW,
  27.                 CW_USEDEFAULT, CW_USEDEFAULT,
  28.                 CW_USEDEFAULT, CW_USEDEFAULT,
  29.                 0, 0, hInst, NULL);
  30.         if (hWnd == 0) return -2;
  31.  
  32.         // 윈도우 초기상태 지정
  33.         ShowWindow(hWnd, nCmdShow);
  34.         UpdateWindow(hWnd);
  35.  
  36.         // 메시지 루프
  37.         while(GetMessage(&msg, 0,0,0))
  38.         {
  39.                 DispatchMessage(&msg);
  40.         }
  41.  
  42.         // WM_QUIT 메시지의 wParam을 종료코드로 한다.
  43.         return msg.wParam;
  44. }
  45.  
  46. LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  47. {
  48.         switch(uMsg)
  49.         {
  50.         case WM_DESTROY:
  51.                 PostQuitMessage(0);
  52.                 return 0;
  53.         }
  54.  
  55.         // 직접 처리하지 않은 메시지는 Windows에게 맡긴다.
  56.         return DefWindowProc(hWnd, uMsg, wParam, lParam);
  57.  
  58. }

 예제의 1번 라인, 앞 부분에는 Wndows.h라고 하는 헤더파일을 인클루드(include)한다. 이헤더파일은 Platform SDK에 포함된 헤더파일로, Win32 API를 C언어 함수에서 호출할때 필요한 프로토타입 선언이 들어있다. 따라서 Win32 API를 사용한 프로그램을 작성할때는 반드시 필요하다.

8번 라인은 WinMain이라는 함수로 프로그램 실행시 최초 실행되는 함수이다. C언어를 배운사람이라면 main() 함수라고 생각하면 된다. WinMain 함수의 인자들을 설명하자면, "hInst" 라는 실행중인 애플리케이션을 식별하는 정수값(인스턴스 핸들)을 받는다. 자주 쓰이는 인자는 아니지만 애플리케이션 내부적으로 필요한 경우가 많으므로 전역변수로 저장해두는 경우가 많다.

 두번째 인자인 hPrevInst도 인스턴스 핸들이지만, Windows3.1과 16비트 Windows와 호환을 위해 남아있다. 보통 0으로 저장되어있고 사용할 일이 없을 것이다. 세번째 인자 lpCmdLine은 명령행 문자열의 포인터이다. main함수의 argv와는 달리 전달인자마다 분리될수 있는 형태가 아니기 떄문에 필요하다면 스스로 문자열을 해석해야한다. CLI 기반이라면 쓸수도 있겠지만, GUI기반 애플리케이션인만큼 이 인자도 자주 쓸일은없다.

 네번째 인자인 nCmdShow는 애플리케이션이 띄우는 메인 위도우의 최초 출력상태를 지정해준다. 이값에 따라 윈도우를 HIDE할수도 있다. 보통 디폴트값을 지정해준다.

5. 윈도우 클래스를 등록해 윈도우를 생성한다

 위에 윈도 예제 소스를 가지고 이어서 설명하도록 하겠다. 22번 라인 을 보면일단 RegisterClass API를 사용해서  윈도우 클래스를 등록한다는 볼수있을 것이다. 등록 하려는 윈도우 클래스 정보를 저장한 WNDCLASS 구조체를 준비해서 인자에 그 주소를 넘겨주면된다. WNDCLASS에 대한 구조체는 아래와 같다.

  1. typedef strcut _WNDCLASS{
  2.         UINT style;     // 윈도우 클래스 스타일
  3.         WNDPROC lpfnWndProc;    //윈도우 프로시저의 주소
  4.         int cbClsExtra;         //윈도우 클래스의 추가 데이터크기
  5.         int cbWndExtra;         //윈도우 추가 데이터 추기
  6.         HINSTANCE hInstance;    // 윈도우 프로시저를 소유한 프로그램의 인스턴스 핸들(대부분 자신)
  7.        
  8.         HICON hIcon;            // 애플리케이션의 아이콘
  9.         HCURSOR hCursor;        // 마우스 커서
  10.         HBRUSH hbrBackground;   // 윈도우 배경색
  11.         LPCTSTR lpszMenuName;   // 메뉴 리소스명
  12.         LPCTSTR lpszClassName;  // 이 윈도우의 클래스 등록명
  13. } WNDCLASS, *PWNDCLASS;

25번 라인은 메인 윈도우 생성이다. CreateWindow API를 사용하는데, 각 인자는 아래와 같다.

  1. HWND CreateWindow(              // 리턴값: 생성된 윈도우 핸들
  2.         LPCTSTR lpClassName,    // 윈도우 클래스명
  3.         LPCTSTR lpWindowName,   // 윈도우 이름
  4.         DWORD dwStyle,          // 윈도우 스타일
  5.         int x,                  // 출력할 윈도우의 좌측 상단 x좌표
  6.         int y,                  // 출력할 윈도우의 좌측 상단 y좌표
  7.         int nWidth,             // 수평방향 크기
  8.         int nHeight,            // 수직방향 크기
  9.         HWND hWndParent,        // 부모 윈도우 핸들
  10.         HMENU hMenu,            // 메뉴 핸들
  11.         HINSTANCE hInstance,    // 윈도우를 생성하는 프로그램의 인스턴스 핸들
  12.         LPVOID lpParam          // WM_CREATE 메시지에 넘길 전달 인자
  13. );

이함수는 윈도우 생성에 성공하면 윈도우 핸들을 반환해준다. 4~7 번째 인자에는 CW_UESDEFAULT 라는 상수값이 지정되어있는데, 이는 윈도우 출력 크기를 Windows에게 맡긴다라는 의미이다. 또한 부모 윈도우와 메뉴 핸들은 0을 넘기면된다.(부모 윈도가 없으므로, 메뉴도 마찬가지)

 33번,34번 라인은 윈도우를 출력해주는 함수로 nCmdShow 라는 값을 넘겨서 윈도우가 보일수 있게 출력해주고 UpdateWindow라는 함수가 윈도우의 변화가 있을경우에 실제 그려주는 API이다.

6. 메시지 루프로 메시지를 하나씩 꺼낸다

 37번 라인 이어서 윈도우를 출력했으면 중요한 메시지 루프를 볼 차례이다. while 문안에서 호출되는 GetMessage는 메시지 큐에서 메시지를 꺼내는 API이다. 첫번째 인자인 MSG 구조체는 수신한 메시지를 저장하는 주소이다. 두번쨰 이후의 인자에는 특정 종류의 메시지만 꺼낼때의 메시지 범위를 지정한다. 보통 0을 지정한다.

 GetMessage의 반환값 형태는 보통 BOOL이며, 꺼낸 메시지가 애플리케이션의 종류를 의미하는 WM_QUIT 인 경우에만 FALSE를 반환한다. 따라서 예제 소스는 WM_QUIT 메시지(종료버튼을 눌렀을때 발생)를 받을때까지 루프가 계손돈다. 39번 라인은 DispatchMessage라는 API 하나만 호출되는데 이는 윈도우 프로시저를 MSG 구조체의 hwnd 멤버를 참조해서 윈도우 프로시저를 호출하도록 해주는 API이다. 메시지 처리를 WndProc에서 하게끔 자동으로 지정해주는 함수라고 보면된다.

7. 메시지의 대부분은 DefWindowProc 함수에 처리를 맡긴다.

 이어서 46번 라인 윈도우 프로시저 함수를 보겠다. 설명했듯이 윈도우 프로시저는 메시지를 처리해주는 함수이다. 16번 라인에서 윈도우를 등록하기전에 프로시저 함수를 지정해준것을 확인할수있다. 단 메시지 처리 방식은 메시지 종류별로 case로 처리하면 되는것이고, 나머지 함수들에 대해서는 DefWindowProc에 이벤트를 넘겨서 처리해주면된다.

 예제는 WM_DESTROY 라는 메시지만 처리하고 나머지는 DdefWindowProc을 호출하여 처리를 맡긴다.


* 참고자료 및 서적

API로 배우는 Windows 구조와 원리

* 다음 링크 참조

http://kaspyx.tistory.com/entry/윈도우Window와-메시지Window-Message

신고
Posted by 캐스피


티스토리 툴바