2007. 2. 15. 21:21
DevX Win32 Assembly Tutorial 6: Keyboard Input

Tutorial 6: Keyboard Input

이 번장에서는, Windows가 어떻게 키보드의 입력을 인식하는지를 설명한다.
   소스       실행 결과   

Theory:

보통, PC에는 키보드가 한개 뿐이므로(두개를 연결하더라도 활성된 키보드는 한개다), 모든 프로그램은, 키보드 자원을 공유해서 사용한다. 그래서, Windows는 현재 활성화된 윈도우에게만 , 키보드의 입력 정보를 보내게 되어 있다.

즉, 화면에 여러개의 윈도우가 있어도, 활성 윈도우에게만 키보드 입력 정보가 보내져서 활성윈도우가 키스트로크(keystroke)를 받게된다. 키스트로크(keystroke)를 받는 윈도우인가 아닌가는, 타이틀바를 보면 알 수있다. 키스트로크(keystroke)를 받는 윈도우의 타이틀바는 하이라이트 되어 있다.

키보드 메세지에는 2개의 메세지 타입이 있다. 먼저, 키보드를 키의 집합으로 생각하는 방식이 있다. 이 경우, 키를 누르면, Windows는 포커스 된 윈도우에 WM_KEYDOWN 메세지를 보내주고, 그 윈도우에 키가 입력되었다고 인식시킨다. 그리고, 키를 놓으면,Windows로부터 WM_KEYUP 메세지가 보내진다. 따라서, 키를 버튼과 같이 다루게 된다(UP/DOWN).다른방식으로는, 키보드를 문자입력장치로 생각하는는 방식이다. 이 경우는"a" 키를 누르면, Windows는 WM_CHAR 메세지를 윈도우에 전하게 되고, "a" 라고 하는 문자도 같이 전달하게 된다(wParam에 키가 전달).

실제로, Windows는 WM_KEYDOWN와 WM_KEYUP의 2개의 메세지를 전송하고, 키보드 메세지를 TarnslateMessage 함수를 사용해서 WM_CHAR 메세지로서 변환한 후 해당 윈도우에 보낸다. 윈도우 프로시저에서 이 메세지들을 모두 처리할 수도 있고, 한가지만 처리 할 수도 있다. 대부분, WM_KEYDOWN 와 WM_KEYUP 메세지는 무시하는 방식을 취한다. 본 예제에서는 WM_CHAR 메세지에 대해서만 다루기로 한다.

Example:

.386
.model flat, stdcall
option casemap:none

WinMain proto :DWORD, :DWORD, :DWORD, :DWORD

include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include \masm32\include\gdi32.inc
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
includelib \masm32\lib\gdi32.lib
.data
ClassName db "SimpleWinClass", 0
AppName db "Our First Window", 0
char WPARAM 20h                        ; the character the program receives from keyboard

.data?
hInstance HINSTANCE ?
CommandLine LPSTR ?

.code
start:
   invoke GetModuleHandle, NULL
   mov   hInstance, eax
   invoke GetCommandLine
    mov CommandLine, eax
   invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT
   invoke ExitProcess, eax

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD
   LOCAL wc:WNDCLASSEX
   LOCAL msg:MSG
   LOCAL hwnd:HWND
   mov  wc.cbSize, SIZEOF WNDCLASSEX
   mov  wc.style, CS_HREDRAW or CS_VREDRAW
   mov  wc.lpfnWndProc, OFFSET WndProc
   mov  wc.cbClsExtra, NULL
   mov  wc.cbWndExtra, NULL
   push hInst
   pop  wc.hInstance
   mov  wc.hbrBackground, COLOR_WINDOW+1
   mov  wc.lpszMenuName, NULL
   mov  wc.lpszClassName, OFFSET ClassName
   invoke LoadIcon, NULL, IDI_APPLICATION
   mov  wc.hIcon, eax
   mov  wc.hIconSm, eax
   invoke LoadCursor, NULL, IDC_ARROW
   mov  wc.hCursor, eax
   invoke RegisterClassEx, addr wc
   invoke CreateWindowEx, NULL, ADDR ClassName, ADDR AppName,\
          WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,\
          CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL,\
          hInst, NULL
   mov  hwnd, eax
   invoke ShowWindow, hwnd, SW_SHOWNORMAL
   invoke UpdateWindow, hwnd
   .WHILE TRUE
               invoke GetMessage, ADDR msg, NULL, 0,0
               .BREAK .IF (! eax)
               invoke TranslateMessage, ADDR msg
               invoke DispatchMessage, ADDR msg
       .ENDW
   mov    eax, msg.wParam
   ret
WinMain endp

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
   LOCAL hdc:HDC
   LOCAL ps:PAINTSTRUCT

   .IF uMsg==WM_DESTROY
       invoke PostQuitMessage, NULL
   .ELSEIF uMsg==WM_CHAR
       push wParam
       pop char
       invoke InvalidateRect, hWnd, NULL, TRUE
   .ELSEIF uMsg==WM_PAINT
       invoke BeginPaint, hWnd, ADDR ps
       mov   hdc, eax
       invoke TextOut, hdc, 0,0, ADDR char, 1
       invoke EndPaint, hWnd, ADDR ps
   .ELSE
       invoke DefWindowProc, hWnd, uMsg, wParam, lParam
       ret
   .ENDIF
   xor   eax, eax
   ret
WndProc endp
end start

Analysis:

char WPARAM 20h  ; the character the program receives from keyboard

이 변수는, 키보드로부터 전달받은 문자 코드를 저장할 변수이다. 그 문자는 윈도우 프로시저의 WPARAM 매개변수로 전달되므로, WPARAM형의 변수를 정의하고 있다. 초기값을 20h 즉 공백문자로 하는 이유는, 윈도우를 처음 표시했을 경우, 키보드로부터 아무것도 입력되지 않기 때문에,공백문자를 넣어준것이다.

.ELSEIF uMsg==WM_CHAR
    push   wParam
    pop    char
    invoke InvalidateRect, hWnd, NULL, TRUE

윈도우 프로시저에서 WM_CHAR 메세지를 처리하고 있다. 예제에서는, char 라는 변수에 입력된 문자를 저장하고, InvalidateRect 함수를 호출하고 있다. InvalidateRect 함수는 해당 윈도우의 작업영역을 강제로 무효영역으로 만들어서, 윈도우 프로시저에 WM_PAINT 메세지를 강제로 발생시키는 함수다.(화면갱신 / 복구)

이 함수의 프로토타입은 다음과 같이 되어있다.

InvalidateRect proto hWnd   :HWND,\
                     lpRect :DWORD,\
                     bErase :DWORD

lpRect 는작업영역의 무효영역을 지정하는 구조체의 포인터로서 , 그영역이 무효영역이 된다. 이 변수가 NULL이면, 작업영역 전체가 무효영역이 된다.

bErase 는 Windows에 배경을 어떻게 처리할 지를 지정하는 플래그다. TRUE 면 BeginPaint 함수가 호출될때에, 무효영역의 배경을 Windows가 제거하게 된다.

결과적으로, 작업영역의 출력에 관한 모든 필요한 정보를 설정해서, 작업영역에 WM_PAINT 메세지를 발생시키게 된다. 물론, 윈도우 프로시저의 WM_PAINT 섹션에서, 메세지를 어떻게 처리할지를 미리 생각해 두어야한다. 이것은 아주 번거롭게 보이지만 윈도우 프로그래밍의 룰이다.

GetDC 함수와 ReleaseDC 함수의 사이에서 WM_CHAR 메세지를 처리함으로서, 작업영역에 출력 하는 것이 가능하다. 거기에는 아무런 문제도 없다. 다만, 작업영역의 화면복구 요구가 발생 했을때, 문제가 발생하게 된다. 문자를 출력하는 부분은 WM_CHAR 섹션이므로, 윈도우 프로시저에서는 작업영역의 화면복구 처리를 할 수가 없는것이다. 그렇기 때문에, (WM_CHAR 섹션의) 가장 마지막에(invoke InvalidateRect, hWnd, NULL, TRUE), WM_PAINT 메세지로 출력 하기 위해서 필요한 데이터와 코드를 써 두는 것이다. 이런 방법을 사용하면, 어떠한 부분에서도 WM_PAINT 메세지를 발생시켜, 언제라도 작업영역의 화면복구를 처리 할 수가 있다.

invoke TextOut, hdc, 0,0, ADDR char, 1

InvalidateRect 함수가 호출되면, 윈도우 프로시저에 WM_PAINT 메세지 발생된다. 그렇기 때문에, WM_PAINT 메세지를 처리하는 부분을 WM_PAINT 섹션이라고 하는 것이다. BeginPaint 함수를 호출해서,DC핸들을 구해서, TextOut 함수로 작업영역의 좌표 x=0 y=0 부분에 문자열을 출력한다. 프로그램을 실행하고, 아무 키나 누르면, 작업영역의 최상단에 입력한 키가 표시된다. 그리고, 윈도우를 일단 최소화하고 나서 최대화하면, 문자는 여전히 출력된 채로 있다. 이것은, WM_PAINT 메세지가 화면을 복구해주고 있는 것이다.


Posted by openserver