Windows Event Message

Kail-KM
|2016. 7. 13. 09:51

 

Windows Application

일반적으로 프로그램이란 실행하면 작성한 코드의 순서대로 동작하는 것으로 생각할 수 있다. 하지만 Windows GUI 응용프로그램은 실행된 후에 단지 윈도우를 출력할 뿐이며, 일반적으로 아무것도 하지 않는다. 이런 프로그램은 사용자가 키보드 입력이나 마우스 버튼 클릭으로 이벤트가 발생되면 그때마다 대응되는 처리를 하는 방식으로 동작한다.

이러한 이벤트 반응형프로그램이 동작하기 위해서는 우선 이벤트가 발생했음을 프로그램에 알리는 구조가 필요하다. Windows에서는 각각의 응용프로그램이 마우스나 키보드로부터 직접 입력을 받지 않는다. 대신 이러한 이벤트가 발생하는지 Windows가 확인한 뒤, 이벤트가 발생했을 때 응용프로그램에 통지한다.

[그림 1] 이벤트 전달 과정

좀 더 자세히 알아보면, 이러한 각 이벤트는 하드웨어의 장치 드라이버가 처리해야 할 일이다. 장치 드라이버는 이벤트를 감지했을 경우 이를 Windows에 알린다. 하지만 Windows가 각 하드웨어의 모든 부분까지 알지는 못하므로 이벤트를 좀 더 단일화된 형태로 변환한 뒤 시스템에 이를 넣는다. 시스템 큐에 추가 된 각 이벤트는 선입선출 방식으로 Windows가 하나씩 꺼내어 해당 응용프로그램에 이벤트를 넘겨준다.

 

Windows Message

사용자로부터 발생한 이벤트에 대해 각 응용프로그램의 큐에 전달된다고 하였다. 좀더 정확하게는 응용프로그램보다도 응용프로그램의 특정 윈도우에 이벤트를 알려준다고 해야한다. 이는 아래 그림과 같이, 하나의 응용프로그램이지만 여러 윈도우를 가진 경우를 생각하면 된다. 사용자가 입력한 키보드 이벤트(WM_KEYDOWN)‘Proc.exe’이란 프로세스에 도착하더라도, 어떤 윈도우가 이를 받아야하는지 나타나지 않는다면 처리가 복잡해진다.

[그림 2] 여러 윈도우를 가진 응용프로그램

이 때문에 사용자 입력 등의 이벤트는 특정 윈도우와 연관된다. Windows 에서 각각의 윈도우에 이벤트를 알리기 위해선 “Window Message”라는 구조체를 이용한다. 구조체의 첫 번째 인자를 보면 각 메시지가 윈도우 핸들을 담고 있다. 이 값을 통해 조작 대상 윈도우를 구별할 수 있다. 그리고 두 번째 인자 Message ID를 통해 어떤 이벤트가 발생했는지 알 수 있다.

typedef struct tagMSG {

  HWND        hwnd;          // Window Handle

  UINT          message;       // Message ID

  WPARAM      wParam;        // Additional information about the message

  LPARAM       lParam;         // Additional information about the message

  DWORD       time;          

  POINT         pt;             // Cursor position

} MSG, *PMSG, *LPMSG;

[코드 1] 윈도우 메시지 구조체

그렇다면 자신에게 온 메시지를 어떻게 확인할까? 위에서 말했다시피 이벤트가 발생하면 해당 응용프로그램 큐(Application Queue)에 메시지가 추가된다고 하였다. 큐에 들어가 있는 메시지는 GetMessage API를 통해 꺼내어 온다. 여기서 주목해야할 인자는 바로 첫 번째 인자로, 인자로 넘겨준 주소에 메시지 정보들을 담아 반환해준다.

BOOL WINAPI GetMessage(

  _Out_    LPMSG  lpMsg,

  _In_opt_  HWND  hWnd,

  _In_      UINT   wMsgFilterMin,

  _In_      UINT   wMsgFilterMax

);

[코드 2] GetMessage API

OllyDBG를 통해 GetMessage API 호출 부분을 확인해보자. 아래의 코드를 보면 함수를 호출하기 위해 각 인자를 Stack에 넣어주는 것을 확인할 수 있다. 여기서 확인해야 할 것은 바로 밑에서 두 번째 줄에 위치한 ‘PUSH EAX’이다. EAX에는 메시지에 대한 정보가 저장될 주소를 담고 있다. 아래 코드에서는 0x4FF8A0인 것을 직접 확인할 수 있다.

Address       Command                             Comments

00B418A1  | PUSH 0                               ; |MsgFilterMax = 0

00B418A3  | PUSH 0                               ; |MsgFilterMin = 0

00B418A5  | PUSH 0                               ; |hWnd = NULL

00B418A7  | LEA EAX,[LOCAL.24]                    ; |

00B418AA  | PUSH EAX                            ; |pMsg : 0x4FF8A0

00B418AB  | CALL DWORD PTR DS:[GetMessage]     ; \USER32.GetMessageW

[코드 3] Disassembly GetMessage

메시지 정보가 반환될 주소를 확인하고 GetMessage API를 호출해보자. 아래의 두 덤프 중 상단의 내용이 하단의 내용으로 바뀌는 것을 확인할 수 있다. 여기서 가장 중요한 것은 바로 메시지 ID0x4FF8A40x100 (WM_KEYDOWN) 메시지가 있는 것을 확인할 수 있다.

Address   Hex dump                                         ASCII

004FF8A0  CC CC CC CC|CC CC CC CC|CC CC CC CC|CC CC CC CC| ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ

004FF8B0  CC CC CC CC|CC CC CC CC|CC CC CC CC|CC CC CC CC| ÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌÌ

Address   Hex dump                                         ASCII

004FF8A0  52 0B 35 00| 00 01 00 00 | 11 00 00 00|01 00 1D 00|   R5       

004FF8B0  68 3B 3B 0D | 7E 03 00 00 |81 01 00 00|CC CC CC CC|    h;;

~ÌÌÌÌ

[코드 4] GetMessage Dump

Hex                 Decimal           Symbolic

0000                0                    WM_NULL

0001                1                    WM_CREATE

0002                2                    WM_DESTROY

0003                3                    WM_MOVE

0100                256                 WM_KEYDOWN

0201                513                 WM_LBUTTONDOWN

020a                522                 WM_MOUSEWHEEL

[ 1] 주요 Message Numbers

결국 메시지 큐에서 꺼내온 메시지는 Window Handle이 가리키는 윈도우에 전송되어 윈도우 프로시저에서 처리한다. 윈도우 프로시저는 Windows에 의해 직접 호출되기 때문에 “CALLBACK” 이라는 타입을 지정해주어야 한다. 첫 번째 인자를 통해 어떠한 Window Handle인지 알 수 있고, 두 번째 인자는 GetMessage를 통해 받은 메시지 ID가 인자로 들어오게 된다..

LRESULT CALLBACK WindowProc(

    HWND   hWnd,        // Window Handle

    UINT     uMsg,        // Message ID

    WPARAM wParam,      // argv 1

    LPARAM  lParam       // argv 2

);

[코드 5] Window Procedure

윈도우 프로시저 처리는 비교문을 통해 프로그래머가 지정한 번호의 메시지가 오는지 확인한다. 아래의 코드를 보면 CMP 명령어를 통해 GetMessage로 받은 Message ID(Local.49)0x100 (WM_KEYDOWN)인지 확인한다. 맞을 경우 사용자가 지정해 놓은 MessageBox 출력을 수행한다.

Address        Command                                  Comments

00B44ED0  |.  CMP DWORD PTR SS:[LOCAL.49], 100        ; WM_KEYDOWN

00B44EDA  |.  JE SHORT 00B44EFF

00B44EFF  |   MOV ESI,ESP

00B44F01  |.  PUSH 40

00B44F03  |.  PUSH OFFSET 00B46C08                  ; |Caption = "Message"

00B44F08  |.  PUSH OFFSET 00B46D08                  ; |Text = "Key Down!"

00B44F0D  |.  MOV EAX,DWORD PTR SS:[ARG.1]         ; |

00B44F10  |.  PUSH EAX                               ; |hOwner => [ARG.1]

00B44F11  |.  CALL DWORD PTR DS:[<MessageBox>     ; \USER32.MessageBox

[코드 6] Disassembly Window Procedure

 

'O / S > Window' 카테고리의 다른 글

네트워크 패킷 캡처 (Windows)  (0) 2020.11.25
Windows Multi Task  (0) 2016.07.08
Windows Service  (0) 2016.06.21
Windows Boot Process (Vista 이상ver 부팅 과정)  (0) 2016.04.13
CSIDL 값  (0) 2016.02.20