IT_Programming/MFC · API

[API] 사용자 정의 메시지 핸들러 & 메시지 크래커

JJun ™ 2009. 7. 29. 09:28

==================================================================================================

 

 [사용자 정의 메시지 핸들러 - 메시지 핸들러 분리]

 

- 메시지 처리 코드를 별도의 함수로 분리!

- 가독성 ↑, 성능 (다소)↓

- 메시지가 발생할 때마다 함수를 호출하면서 오버헤드 현상이 일어난다.

  그렇기 때문에 case문에 처리코드를 넣는 것보다는 성능에 손해가 발생한다.

  하지만 초당 20억 ~ 40억개 이상의 명령을 처리하는 요즘 CPU 속도를 감안하면

  이 정도의 속도 손실은 무시할 만하다. 오히려 사람이 작업하기 편다는 측면에서 사용할 만하다.

 

 

------------------------------------------------------------------------------------------------   

 

                                    

[메시지 크랙커]

 

- HANDLE_MSG 매크로를 사용해서 " case 메시지 : return 함수 (인자); " 형태로 치환하는 형태의 구현

- windowsx.h 헤더 파일을 포함해야 한다.

- 윈도우 프로시저의 WPARAM wParam, LPARAM lParam의 인자 이름을 바꿔서는 안된다.

 

- 장점 : 1) 운영체제가 업그레이드되면 메시지의 추가 정보가 바뀔 수 있는데

                메시지 크랙커는 이런 변화에 충분히 대응할 수 있도록 작성되어 있다.

            2) 소스 길이가 길면 길수록 메시지 크랙커의 가치가 더 빛난다.

            3) 더이상 wParam, lParam의 의미에 대해 신경 쓸 필요가 없이 오로지 메시지 크랙커의 인자에만

                신경써서 구현할 수 있다.

            4) 비슷한 함수는 핸들러를 통합

 

- 단점 : 1) 헤더 파일에서 함수 원형을 복사해 오는 방법밖에 없다.

            2) Win32 API의 0, -1을 좀 더 인간적이고 이해하기 쉬운 TRUE, FASLE로 바꾸려하다보니

                이를 미리 숙지 못할 경우 엉뚱한 코드를 작성할 위험이 있다.

            3) 메시지 크랙커를 사용하려면 또 다른 함수 집합을 익혀야 한다.

            4) 소스 코드가 짧을 경우 오히려 더 번거로워질 수도 있다.

            5) 일부 매크로에 버그 존재

        

- 동작과정  (windowsx.h)

  1) switch문의 " case 메시지 : " 구문을 만들어낸다.

      #define HANDLE_MSG(hwnd, message, fn) \

          case (message):

               return HANDLE_##message((hwnd), (wParam), (lParam), (fn))

 

  2) "return 함수(인자)" 형태의 구문을 만든다. (ex : WM_SIZE)

      #define HANDLE_WM_SIZE(hwnd, wParam, lParam, fn) \ 

         ((fn) ((hwnd), (UINT)(wParam), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam), 0L) 

 

      → 최종 결과 : case WM_SIZE :

                              return Msg_Crk(hwnd, wParam, LOWORD(lParam), HIWORD(lParam))

 

 

- 메시지 크랙커 작성 순서

 

  1) switch문 안에 처리하고자 하는 메시지에 대해 HANDLE_MSG(핸들, 메시지, 처리함수명) 매크로

     구문을 삽입!

 

  2) 함수의 본체를 만든다. (메시지 처리 함수의 원형은 windowsx.h에 있다.)

      ex) "WM_SIZE"의 경우 

           /* void Cls_OnSize(HWND hwnd, UINT state, int cx, int cy) */
           #define HANDLE_WM_SIZE(hwnd, wParam, lParam, fn) \
           ((fn)((hwnd), (UINT)(wParam), (int)(short)LOWORD(lParam), (int)(short)HIWORD(lParam)), 0L)

 

  ※   함수를 만들 때 매크로에 대응하는 함수 fn을 만든다. (ex : WM_SIZE)

        void Cls_OnSize(HWND, UINT state, int cx, int cy) // Cls_ == 메시지를 받을 윈도우_               

 

==================================================================================================

 

[실행화면] 

 

 

--------------------------------------------------------------------------------------------------

 

[ 내용 참조 및 예제 출처 - 윈도우즈 API 정복 1권 ]

==================================================================================================

 

[소스코드 - 사용자 정의 메시지 핸들러]

 

#include <windows.h>

 

#define ELAPSE  1000
#define TIMER_ID 1
#define BUF_SIZE 1024

 

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
/* 사용자 정의 메시지 핸들러 */

LRESULT OnCreate(HWND, UINT, WPARAM, LPARAM);
LRESULT OnDestroy(HWND, UINT, WPARAM, LPARAM);
LRESULT OnPaint(HWND, UINT, WPARAM, LPARAM);
LRESULT OnKeyDown(HWND, UINT, WPARAM, LPARAM);
LRESULT OnLButtonDown(HWND, UINT, WPARAM, LPARAM);
LRESULT OnTimer(HWND, UINT, WPARAM, LPARAM);
LRESULT OnSize(HWND, UINT, WPARAM, LPARAM);

 

int APIENTRY WinMain ( HINSTANCE hInstance,

                                   HINSTANCE hPrevInstance,

                                   LPSTR lpCmdLine,

                                   int nShowCmd)
{
      static char MainClassName[] = TEXT("WinMain");
      HWND  hwnd;
      MSG   msg;
      WNDCLASS wndclass;

 

      wndclass.cbClsExtra       = 0;
      wndclass.cbWndExtra      = 0;
      wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      wndclass.hCursor           = LoadCursor(NULL, IDC_ARROW);
      wndclass.hIcon               = LoadIcon(NULL, IDI_APPLICATION);
      wndclass.hInstance         = hInstance;
      wndclass.lpfnWndProc      = WndProc;
      wndclass.lpszClassName = MainClassName;
      wndclass.lpszMenuName = NULL;
      wndclass.style                 = CS_HREDRAW | CS_VREDRAW;

 

      RegisterClass(&wndclass);

 

      hwnd = CreateWindow (
                                          MainClassName,
                                          MainClassName,
                                          WS_OVERLAPPEDWINDOW,
                                          CW_USEDEFAULT,
                                          CW_USEDEFAULT,
                                          CW_USEDEFAULT,
                                          CW_USEDEFAULT,
                                          NULL,
                                          (HMENU) 0,
                                          hInstance,
                                          NULL
                                    );

 

      ShowWindow(hwnd, nShowCmd);

     

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

      return (int)msg.wParam;
}

 

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
      switch(message) // 사용자 정의 메시지 핸들러를 사용함으로써 달라지는 switch문
      {
            case WM_CREATE  : return onCreate(hwnd, message, wp, lp);
            case WM_LBUTTONDOWN : return onLButtonDown(hwnd, message, wp, lp);
            case WM_KEYDOWN  : return onKeyDown(hwnd, message, wp, lp);
            case WM_PAINT  : return onPaint(hwnd, message, wp, lp);
            case WM_TIMER  : return onTimer(hwnd, message, wp, lp);
            case WM_SIZE  : return onSize(hwnd, message, wp, lp);
            case WM_DESTROY  : return onDestroy(hwnd, message, wp, lp);
      }

      return (DefWindowProc(hwnd, message, wp, lp));
}

 

/* 사용자 정의 메시지 핸들러 구현 */

LRESULT OnCreate(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
      SetTimer(hwnd, TIMER_ID, ELAPSE, NULL);

      return 0;
}

 

LRESULT OnDestroy(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
      KillTimer(hwnd, TIMER_ID);
      PostQuitMessage(0);

      return 0;
}

 

LRESULT OnPaint(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
      PAINTSTRUCT ps;
      TCHAR  *msg = TEXT("사용자 메시지 처리 핸들러 구현!");
      HDC   hdc  = BeginPaint(hwnd, &ps);
      TextOut(hdc, 10, 10, msg, lstrlen(msg));
      EndPaint(hwnd, &ps);

      return 0;
}

 

LRESULT OnKeyDown(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
      RECT rect;
      TCHAR str[BUF_SIZE];
      HDC  hdc = GetDC(hwnd);
      SetRect(&rect, 10, 80, 500, 100);
      FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
      wsprintf(str, TEXT("%c 키를 누르셨습니다!"), wp);
      TextOut(hdc, 10, 80, str, lstrlen(str));
      ReleaseDC(hwnd, hdc);

      return 0;
}

 

LRESULT OnLButtonDown(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
      RECT rect;
      TCHAR str[BUF_SIZE];
      HDC  hdc = GetDC(hwnd);
      SetRect(&rect, 10, 100, 500, 120);
      FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
      lstrcpy(str, "마우스 왼쪽 버튼을 누르셨습니다!");
      TextOut(hdc, 10, 100, str, lstrlen(str));
      ReleaseDC(hwnd, hdc);

      return 0;
}

 

LRESULT OnTimer(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
      RECT     rect;
      TCHAR   str[BUF_SIZE];
      static int count = 0;
      HDC       hdc = GetDC(hwnd);
      SetRect(&rect, 10, 60, 500, 80);
      FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
      wsprintf(str, TEXT("%d초 경과했습니다!"), count++);
      TextOut(hdc, 10, 60, str, lstrlen(str));
      ReleaseDC(hwnd, hdc);

      return 0;
}

LRESULT OnSize(HWND hwnd, UINT message, WPARAM wp, LPARAM lp)
{
      RECT rect;
      TCHAR str[BUF_SIZE];
      HDC  hdc = GetDC(hwnd);
      SetRect(&rect, 10, 40, 500, 60);
      FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
      wsprintf(str, TEXT("현재 작업 영역의 크기: %d*%d!"), LOWORD(lp), HIWORD(lp));
      TextOut(hdc, 10, 40, str, lstrlen(str));
      ReleaseDC(hwnd, hdc);

      return 0;
}

 

-----------------------------------------------------------------------------------------------

 

[소스코드 - 메시지 크래커]

 

 #include <windows.h>
#include <windowsx.h> // 메시지 크래커를 사용하기 위해 추가하는 헤더파일

 

#define ELAPSE  1000
#define TIMER_ID 1
#define BUF_SIZE 1024

 

/* 메시지 크랙커 - windowsx.h에 명시된 형태로 매개인자를 적어준다. */
BOOL MsgCrk_OnCreate(HWND, LPCREATESTRUCT);
void MsgCrk_OnDestroy(HWND);
void MsgCrk_OnPaint(HWND);
void MsgCrk_OnKey(HWND, UINT, BOOL, int, UINT);
void MsgCrk_OnLButtonDown(HWND, BOOL, int, int, UINT);
void MsgCrk_OnTimer(HWND, UINT);
void MsgCrk_OnSize(HWND, UINT, int, int);

 

/* 윈도우즈 콜백함수 */
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

 

int APIENTRY WinMain (HINSTANCE hInstance,

                                  HINSTANCE hPrevInstance,

                                  LPSTR lpCmdLine,

                                  int nShowCmd)
{
      static char MainClassName[] = TEXT("MsgCrk");
      HWND  hwnd;
      MSG   msg;
      WNDCLASS wndclass;

 

      wndclass.cbClsExtra  = 0;
      wndclass.cbWndExtra  = 0;
      wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
      wndclass.hCursor  = LoadCursor(NULL, IDC_ARROW);
      wndclass.hIcon   = LoadIcon(NULL, IDI_APPLICATION);
      wndclass.hInstance  = hInstance;
      wndclass.lpfnWndProc = WndProc;
      wndclass.lpszClassName = MainClassName;
      wndclass.lpszMenuName = NULL;
      wndclass.style   = CS_HREDRAW | CS_VREDRAW;

 

      RegisterClass(&wndclass);

     

      hwnd = CreateWindow (
                                          MainClassName,
                                          MainClassName,
                                          WS_OVERLAPPEDWINDOW,
                                          CW_USEDEFAULT,
                                          CW_USEDEFAULT,
                                          CW_USEDEFAULT,
                                          CW_USEDEFAULT,
                                          NULL,
                                          (HMENU) 0,
                                          hInstance,
                                          NULL
                                    );

 

      ShowWindow(hwnd, nShowCmd);

 

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

      return (int)msg.wParam;
}

 

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
      switch(message) // 메시지 크랙커를 사용함으로써 달라지는 switch문
      {
            HANDLE_MSG(hwnd, WM_CREATE,   MsgCrk_OnCreate);
            HANDLE_MSG(hwnd, WM_LBUTTONDOWN, MsgCrk_OnLButtonDown);
            HANDLE_MSG(hwnd, WM_KEYDOWN,  MsgCrk_OnKey);
            HANDLE_MSG(hwnd, WM_PAINT,   MsgCrk_OnPaint);
            HANDLE_MSG(hwnd, WM_TIMER,   MsgCrk_OnTimer);
            HANDLE_MSG(hwnd, WM_SIZE,   MsgCrk_OnSize);
            HANDLE_MSG(hwnd, WM_DESTROY,  MsgCrk_OnDestroy);
      }

      return (DefWindowProc(hwnd, message, wParam, lParam));
}

 

/* 메시지 크랙커에 등록한 함수 구현 */

BOOL MsgCrk_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
{
      SetTimer(hwnd, TIMER_ID, ELAPSE, NULL);
      return TRUE;
}

 

void MsgCrk_OnDestroy(HWND hwnd)
{
      KillTimer(hwnd, TIMER_ID);
      PostQuitMessage(0);
}

 

void MsgCrk_OnPaint(HWND hwnd)
{
      PAINTSTRUCT ps;
      TCHAR  *msg = TEXT("메시지 크랙커 구현!");
      HDC   hdc  = BeginPaint(hwnd, &ps);

      TextOut(hdc, 10, 10, msg, lstrlen(msg));
      EndPaint(hwnd, &ps);
}

 

void MsgCrk_OnKey(HWND hwnd, UINT vk, BOOL fDown, int cRepeat, UINT flags)
{
     //  메시지 크래커는 비슷한 함수는 핸들러를 통합하기도 한다.
     // BOOL fDown : WM_KEYUP, WM_KEYDOWN 메시지 구분 용도
      RECT rect;
      TCHAR str[BUF_SIZE];
      HDC  hdc = GetDC(hwnd);
      SetRect(&rect, 10, 80, 500, 100);
      FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
      wsprintf(str, TEXT("%c 키를 누르셨습니다!"), vk);
      TextOut(hdc, 10, 80, str, lstrlen(str));
      ReleaseDC(hwnd, hdc);
}

 

void MsgCrk_OnLButtonDown(HWND hwnd, BOOL fDoubleClick, int x, int y, UINT keyFlags)
{
      RECT rect;
      TCHAR str[BUF_SIZE];
      HDC  hdc = GetDC(hwnd);
      SetRect(&rect, 10, 100, 500, 120);
      FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
      lstrcpy(str, "마우스 왼쪽 버튼을 누르셨습니다!");
      TextOut(hdc, 10, 100, str, lstrlen(str));
      ReleaseDC(hwnd, hdc);
}

 

void MsgCrk_OnTimer(HWND hwnd, UINT id)
{
      RECT  rect;
      TCHAR  str[BUF_SIZE];
      static int count = 0;
      HDC   hdc = GetDC(hwnd);
      SetRect(&rect, 10, 60, 500, 80);
      FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
      wsprintf(str, TEXT("%d초 경과했습니다!"), count++);
      TextOut(hdc, 10, 60, str, lstrlen(str));
      ReleaseDC(hwnd, hdc);
}

 

void MsgCrk_OnSize(HWND hwnd, UINT state, int cx, int cy)
{
      RECT rect;
      TCHAR str[BUF_SIZE];
      HDC  hdc = GetDC(hwnd);
      SetRect(&rect, 10, 40, 500, 60);
      FillRect(hdc, &rect, (HBRUSH)GetStockObject(WHITE_BRUSH));
      wsprintf(str, TEXT("현재 작업 영역의 크기: %d*%d!"), cx, cy);
      TextOut(hdc, 10, 40, str, lstrlen(str));
      ReleaseDC(hwnd, hdc);
}

 

==================================================================================================