검색결과 리스트
assembly에 해당되는 글 5건
글
튜토리얼 6: Keyboard Input
이번 시간에는 Windows 프로그램에서 키보드 입력을 받는 방법에 대해 알아 보도록 하겠습니다.
Theory:
보통 PC 하나에 키보드가 하나이기 때문에, 실행중인 Windows 프로그램은 서로 키보드를 공유해야만 합니다. 현재 입력 포커스를 가지고 있는 윈도우에게 키 입력을 전달할 임무는 Windows 가 가지고 있습니다.
비록 화면에 여러개의 윈도우가 떠 있더라도, 그 중 단 하나만이 입력 포커스를 가지고 있습니다. 입력 포커스를 가지고 있는 이 한 개의 윈도우만이 키보드 입력을 받을 수 있습니다. 이 윈도우는 타이틀 바를 보면 알 수 있습니다. 입력 포커스를 가지고 있는 윈도우의 타이틀바는 하이라이팅 되어 있기 때문이죠.
실제로 키보드 메세지에는 두가지가 있습니다. 이것은 키보드를 보는 관점에 따라 달라집니다. 하나는 키보드를 키의 집합으로 보는 것입니다. 이 경우 키를 눌렀을 때, Windows 는 WM_KEYDOWN 메세지를 입력 포커스를 가지고 있는 윈도우에게 보내서 키가 눌러졌음을 알립니다. 키를 땐 경우에는, Windows 는 WM_KEYUP 메세지를 보냅니다. 키를 버튼이라고 생각하는 것이죠. 다른 관점에서 보면 키보드는 문자를 입력하는 장치입니다. "a" 를 눌렀을 때, Windows 는 WM_CHAR 메세지를 입력 포커스를 가지고 있는 윈도우에게 전달함으로써 사용자가 "a" 문자를 입력했음을 알려줍니다. 사실 Windows 는 WM_KEYDOWN 과 WM_KEYUP 메세지를 입력 포커스를 가지고 있는 윈도우에게 전달하고, 이 메세지들은 TranslateMessage 호출에 의해서 WM_CHAR 메세지로 변경될 것입니다. 윈도우 프로시져에 따라 이 세개의 메세지를 모두 사용 할수도 있고, 아니면 한개의 메세지만 사용할 수 있습니다. 대부분의 경우, 메세지 루프에서 TranslateMessage 함수를 호출하면 WM_KEYDOWN 과 WM_KEYUP 를 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
배경을 지워야 할지를 나타내는 플래그입니다. 만약 플래그 값을 TRUE 로 하면, Windows 는 BeginPaint 가 호출 되었을 때, 유효하지 않은 사각 영역의 배경을 지워버립니다.
여기서 우리의 의도는 다음과 같습니다. 클라이언트 영역을 그리는데 필요한 모든 정보를 저장합니다. 그리고 클라이언트 영역을 그리기 위해서 WM_PAINT 메세지를 생성합니다. 물론 WM_PAINT 섹션에서는 무엇을 해야 하는지 미리 알고 있어야 합니다. 빙 돌면서 일을 처리하는 것 같지만 이것이 Windows 방식입니다.
사실 GetDC 와 ReleaseDC 를 이용하면, WM_CHAR 메세지를 받았을 때 클라이언트 영역에 그리도록 할 수 있습니다. 그리고 문제가 될 것이 없습니다. 하지만 문제는 윈도우가 클라이언트 영역을 다시 그려야 할 때 발생합니다. 문자를 그리는 코드가 WM_CHAR 섹션에 있기 때문에, 윈도우 프로시져는 우리의 문자를 클라이언트 영역에 다시 그릴 수가 없습니다. 그래서 마지막 라인처럼 WM_PAINT 에 필요한 모든 데이터와 그리는 코드를 넣었습니다. 덕분에 우리는 클라이언트 영역을 다시 그리고 싶을 땐, 코드 어디에서든 언제든지 WM_PAINT 메세지만 보내면 됩니다.
invoke TextOut,hdc,0,0,ADDR char,1
InvalidateRect 함수가 호출되면, WM_PAINT 메세지가 윈도우 프로시져로 전달됩니다. 그리고 WM_PAINT 섹션의 코드가 호출되죠. 이것은 디바이스 컨텍스트 핸들을 얻기 위한 BeginPaint 와 클라이언트 영역 x=0, y=0 위치에 문자를 그리기 위한 TextOut 을 호출합니다. 프로그램을 실행하고 아무키나 눌러 보면, 윈도우의 좌측 상단 구석에 문자가 찍히는 것을 보실 수 있습니다. 그리고 그리는데 필요한 모든 데이터와 코드가 WM_PAINT 섹션 안에 들어 있기 때문에, 창을 최소화 했다가 다시 최대화 시켜도 여전히 문자가 그려져 있는 것을 확인 하실 수 있습니다.
Epilogue:
이전에 말씀 드렸듯이, 이 강좌는 영문으로 된 원서를 번역하고 있는 것입니다용~ 오해 없으시기 바랍니다.
어셈블리가 처음에는 어렵지만 나중에 뒤로 갈수록 C/C++ 과 전혀 다를게 없다라는 생각이 마구마구 듭니다. 왜냐면 메크로 어셈블리기 때문이죠. ㅎㅎ 아무튼 드리고 싶은 말씀은... 그냥 넋두리입니다. 오늘은 2010 년 12월 31일! 마지막 날입니다. 내일이면 2011년 이네요. 한 해 동안 무엇을 하였나 다시한번 생각해 봅니다. 엇.. 저녁 시간입니다. 저녁먹고 오겠습니다. 여러분 2010년 한해 마무리 잘 하시고, 2011년 준비 잘 하시기 바랍니다~
그럼 전 이만~
'Microsoft > MASM32' 카테고리의 다른 글
전역후킹(Global Hooking) (0) | 2011.01.23 |
---|---|
[MASM 강좌] 튜토리얼 7 : Mouse Input (0) | 2010.12.12 |
[MASM 강좌] 튜토리얼 5 : More about Text (0) | 2010.12.12 |
[MASM 강좌] 튜토리얼 4 : Painting with Text (0) | 2010.11.29 |
[MASM 강좌] 튜토리얼 3 : A Simple window (0) | 2010.11.29 |
RECENT COMMENT