API 메시지

IT/개발공부 / / 2020. 9. 6. 22:12
728x90
반응형

윈도우 프로시저

윈도우가 이전 운영체제인 도스와 구분되는 가장 큰 차이점은 메시지 기반의 운영체제라는 겁니다. 윈도우에서 실행되는 응용 프로그램은 사용자 입력을 받기 위해 함수를 직접적으로 호출하는 경우가 없으며 시스템이 보내는 메시지를 기다릴 뿐입니다.

 

윈도우 프로시저는 윈도우 클래스당 하나씩 배정되며 메시지에 대응하는 방식을 정의함으로써 윈도우의 행동 양식을 결정합니다. 

 

윈도우 프로시저는 아래와 같은 원형을 가집니다.

LRESULT CALLBACK WndProc(HWND hWNd, UINT iMessage, WPARAM, wParam, LPARAM lParam);

문법적으로 함수이며 이름은 사용자가 마음대로 바꿀 수 있습니다. 단 몇가지 일반 함수와 차이점이 있는데

  • 리턴타입과 인자를 별경할 수 없습니다.
  • 운영체제에 의해 호출되는 콜백함수입니다. 따라서 함수명 앞에 __stdcall을 추가합니다.
  • 사용자가 직접 함수의 이름을 호출 할 수 없습니다. 만약 호출이 필요하다면 SendMessage 등의 함수로 메시지를 보내 간접적으로 호출해야 합니다.

인수의 내용은 아래와 같습니다.

인수 내용
hWnd 이 메시지를 받을 윈도우 핸들 입니다. 한 클래스로부터 여러 개의 윈도우가 만들어 졌을 경우는 어떤 윈도우로 전달된 메시지인지 구분해야 하므로 이 인수가 필요합니다.
iMessage 전달된 메시지 종류입니다. WM_CREATE, WM_PAINT 등의 미리 정해진 매크로 상수값을 사용하여 어떤 메시지가 전달되었는지 구분합니다.
wParam
IParam
2번째 인자의 메시지별 부가적인 정보입니다. 둘 다 32비트 정수값이며 메시지에 따라 이값들의 의미는 달라집니다.

 

윈도우 프로시저는 이 함수로 전달되는 무수히 많은 메시지들을 처리하므로 거대한 switch 문으로 구성됩니다. 처리하는 메시지가 많으면 많을수록 case 문이 많아질 것이며, 처리하지 않는 메시지는 반드시 DefWindowProc 으로 전달해야 합니다.

 

일반적으로 처리된 메시지 결과로 0을 리턴합니다.

 

메시지 큐

메시지는 크게 메시지 큐로 들어가는 큐 메시지와 메시지 큐에 들어가지 않고 곧바로 윈도우 프로시저로 보내지는 비큐 메시지로 구분됩니다.

 

비큐 메시지

비큐 메시지는 윈도우에게 특정 사실을 알리거나 명령을 내리기 위해 큐를 통하지 않고 바로 윈도우 프로시저로 보내지는 메시지입니다. 

비큐메시지 (WM_CREATE) 흐름

  1. 사용자가 윈도우를 만들기 위해 CreateWindowEx API를 호출하면 윈도우가 만들어지고
  2. 윈도우가 만들어졌다는 사실을 사용자에게 알리기 위해 WM_CREATE 라는 메시지가 메시지 처리 함수에 전달됩니다.
  3. WM_CREATE 메시지 처리가 완료되면 CreateWindowEx 함수는 윈도우 핸들을 리턴합니다.

 

WM_CREATE 메시지는 메시지 큐에 놓이지 않고 직접 윈도우 프로시저에 전달됩니다. 이를 비큐 메시지라고 합니다.

 

입력메시지 ( 마우스 , 키보드 메시지 )를 제외한 대부분의 메시지가 비큐 메시지입니다.

시점 비큐 메시지
CreateWindow WM_CREATE
MoveWindow WM_SIZE, WM_MOVE
DestroyWindow WM_DESTROY
활성 상태 변경 WM_ACTIAVTE

 

큐 메시지

마우스 키보드 메시지들은 직접 윈도우 프로시저로 전달되지 않고 메시지 큐에 놓이게 되는데 이를 큐 메시지라고 합니다.

큐 메시지

큐 메시지는 주로 사용자 입력으로 발생되는 데 WM_KEYDOWN, WM_LBUTTONDOWN 등이 대표적이며 그 외 WM_PAINT, WM_TIMER , WM_QUIT 등이 큐 메시지입니다. 큐 메시지는 발생 직후 시스템 메시지 큐에 저장되어 스레드 메시지 큐로 보내지며 최종적으로 윈도우 프로시저에 의해 입력된 순서대로 처리됩니다.

 

큐에 놓인 메시지를 처리하려면 반드시 사용자가 큐에 놓여 있는 메시지를 가져오는 메시지 루프가 필요합니다. GetMessage API가 그 일을 수행합니다.

 

MSG msg;
while ( GetMessage( &msg, 0, 0, 0 ) ) 
{
      TranslateMessage(&msg);
      DispatchMessage(&msg);
}

GetMessage API는 메시지 큐에 WM_QUIT 메시지가 있을 경우에만 TRUE 값을 리턴합니다. 그래서 위 코드는 WM_QUIT 메시지가 큐에 들어올 때 까지는 무한 루프가 됩니다.

 

프로그램 종료

사용자가 만든 윈도우가 파괴될때 나오는 메시지는 WM_DESTROY 입니다. 그러므로 사용자가 윈도우를 파괴 해도 프로그램은 계속 메시지 루프를 돌게 됩니다.

 

윈도우를 1개 만든 경우 프로그램 main 윈도우가 파괴 될때 프로그램을 같이 종료 되게 하려면 WM_DESTROY 메시지에서 WM_QUIT 메시지를 메시지 Q에 넣는 작업을 해야 합니다.

 

case WM_DESTROY:
     PostQuitMessage( 0 );
     return 0;

 

처리하지 않는 메시지

메시지 처리 함수에서 발생된 메시지를 처리하지 않은 경우 반드시 아래 함수로 보내서 default 처리가 되게 해야 합니다. 

LRESULT CALLBACK DefWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);

해당 함수 호출시 인자는 윈도우 프로시저에 전달된 인자를 그대로 전달합니다.

 

 

API Skeleton ( 스켈레톤 ) 코드

API를 이용한 프로그래밍 기본 스켈레톤 코드입니다.

#include <windows.h>
#include <tchar.h>
LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
       switch( msg ) {
       case WM_CREATE:
       return 0;
       case WM_DESTROY:
           PostQuitMessage(0);
           return 0;
       }
       return DefWindowProc(hwnd, msg, wParam, lParam);
}

int WINAPI _tWinMain(HINSTANCE hInst, HINSTANCE hPrev, LPTSTR lpCmdLine, int nShowCmd)
{
      // 1. 윈도우 클래스 만들기
      WNDCLASS wc;
      wc.cbWndExtra = 0;
      wc.cbClsExtra = 0;
      wc.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      wc.hCursor = LoadCursor(0, IDC_ARROW);
      wc.hIcon = LoadIcon(0, IDI_APPLICATION);
      wc.hInstance = hInst;
      wc.lpfnWndProc = WndProc;
      wc.lpszClassName = TEXT("First");
      wc.lpszMenuName = 0;
      wc.style = 0;
      
      // 2. 등록(레지스트리에)
      RegisterClass(&wc);
      // 3. 윈도우 창 만들기
      HWND hwnd = CreateWindowEx( 0,                   // WS_EX_TOPMOST
                  TEXT("first"),                       // 클래스 명
                  TEXT("Hello"),                       // 캡션바 내용
                  WS_OVERLAPPEDWINDOW,
                  CW_USEDEFAULT , 0, CW_USEDEFAULT, 0, // 초기 위치
                  0, 0,                                // 부모 윈도우 핸들, 메뉴 핸들
                  hInst,                               // WinMain의 1번째 파라미터 (exe 주소)
                  0);                                  // 생성 인자
      // 4. 윈도우 보여주기
      ShowWindow(hwnd, SW_SHOW);
      UpdateWindow(hwnd);
      
      // 5. Message
      MSG msg;
      while( GetMessage( &msg, 0, 0, 0 ) )
      {
          TranslateMessage(&msg);
          DispatchMessage(&msg);
      }
      return 0;
}

 

PeekMessage

메시지 루프에서 제일 중요한 함수는 메시지를 가져오는 GetMessage 함수입니다. 이 함수는 스레드 미시지 큐에서 메시지를 가져오는데 메시지가 없으면 새로운 메시지가 전달 될 때까지 리턴하지 않습니다.

 

만약 메시지 큐에 메시지가 없을 경우 다른 작업을 하고 싶다면 PeekMessage API를 사용하면 됩니다.

BOOL PeekMessage(LPMSG lpMsg, HWND hwnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg);

이 함수는 메시지 큐에서 메시지를 꺼내거나 검사하되 메시지가 없더라도 즉각 리턴합니다. 리턴값이 TRUE 이면 메시지가 있다는 뜻이고 FASLE 이면 메시지가 없다는 뜻입니다. wRemoveMsg 는 메시지가 있을 경우 이 메시지를 큐에서 제거할 것인지 아닌지를 지정하는 데 PM_REMOVE 이면 큐에서 메시지를 제거하고, PM_NOREMOVE 이면 제거하지 않을 수도 있습니다.

// 5. Message
MSG msg;
while(true)
{
   if( PeekMessage( &msg, 0, 0, 0, PM_REMOVE ) )
   {
       if( msg.message == WM_QUIT) break;
       TranslateMessage(&msg);
       DispatchMessage(&msg);
   }
   
   else
   {
       HDC hdc = GetDC(hwnd);
       SetPixel(hdc, rand()%500, rand()%400, RGB(rand()%256, rand()%256, rand()%256));
       ReleaseDC(hwnd, hdc);
   }
}

 

사용자 정의 메시지

메시지 범위

메시지의 명칭은 흔히 WM_PAINT, WM_CREATE 등과 같은 매크로 상수로 나타내는데 이 매크로의 실제 값은 정수로 정의되어 있습니다. 메시지간의 구분을 위한 표식이므로 중복되지만 않으면 되고 이런 목적에는 정수형이 제일 적합합니다.

 

메시지의 ID의 실제값은 Winuser.h 파일에 다음과 같이 정의되어 있습니다.

메시지
WM_CREATE 1
WM_DESTROY 2
WM_MOVE 3
WM_LBUTTONDOWN 0x201

프로그래밍을할 때는 보통 WM_PAINT 등의 매크로 상수만을 사용하므로 메시지 ID의 실제 상수값은 굳이 몰라도 상관없습니다. 하지만 사용자 정의 메시지를 만드려면 나만의 메시지를 만들어야 하고, 그 때 기존 메시지의 ID값과 충돌이 나면 안됩니다.

 

메시지 ID 데이터형은 부호가 없는 정수형인 UINT형으로 범위는 2의 32승입니다.

 

사용자 정의 메시지

응용 프로그램은 시스템이 정의한 메시지 외에 자신의 필요함에 따라 고유의 메시지를 만들어 사용할 수 있습니다. 메시지는 약속이기 떄문에 정하기 나름이며 wParam, IParam 의 용도도 편한대로 정해 사용할 수 있습니다.

 

사용자 정의 메시지를 사용하기 위해서는 다음과 같은 절차를 거칩니다.

  1. 나만의 메시지를 정의한다.
  2. 메시지 프로시저에서 해당 메시지를 필터링한다.
  3. 원하는 시점에서 SendMessage 를 사용하여 정의 메시지를 호출한다.
#include <windows.h>
#include <tchar.h>
#define WM_MYMESSAGE WM_USER+100

LRESULT CALLBACK WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
        switch( msg )
        {
           case WM_MYMESSAGE:
           {
               TCHAR buf[20];
               wsprintf(buf, TEXT("%d+%d=%d"), wParam, lParam, wParam+lParam);
               MessageBox(hwnd, buf, TEXT(""), MB_OK);
           }
           return 0;
           
           case WM_LBUTTONDOWN:
               SendMessage(hwnd, WM_MYMESSAGE, 10, 20);
               return 0;
               
           case WM_CREATE:
               return 0;
           
           case WM_DESTROY:
               PostQuitMessage(0);
               return 0;
        }
        return DefWindowProc(hwnd, msg, wParam, lParam);
}

 

728x90
반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기