2007. 2. 15. 17:49
DevX Win32 ASM tutorial 4: Painting with Text

Tutorial 4: Painting with Text

이번장에는, 윈도우의 작업영역에 텍스트를표시하는것을 설명한다. 또한, 디바이스 컨텍스트에(DC) 대해서도 알게 될 것이다.
   소스       실행 결과   

Theory:

Windows 에서 텍스트는 GUI 오브젝트중의 하나이다. 각각의 글자는, 여러개의 픽셀로 구성되고, 그 픽셀의 모임으로 하나의 인식할 수 있는 문자가 된다. 그렇기 때문에, 글자를쓴다는것 보다는,그린다라는 표현이 맞다. 보통의 경우, 프로그래머가 생성한 윈도우의 작업영역에 텍스트를 그린다(작업영역 바깥부분에도 그릴 수 있다). Windows의 화면에 문자를 출력하는 것은, DOS경우와는 완전히 다른 방법이 사용된다. DOS에서는, 80×25 의 화면 좌표만 신경쓰면 되었지만, Windows에서는, 화면에 여러개의 프로그램이 실행되는 환경이므로, 각각의 윈도우가 겹쳐지게 되는 경우도 있기때문에, 몇가지의 룰을 지켜야한다. Windows는, 각각의 윈도우가, 자신의 작업영역에만 다시그리기(화면복구)를 하도록 구조화 되어있다. 다만, 윈도우의 작업영역의 사이즈는 일정하지 않으며 사용자는 언제라도 크기를 변경할 수있다. 그렇기 때문에, 그때 마다 작업영역의 사이즈를 변경해 주어야 한다.

작업영역에 무엇인가를 그리기 전에, Windows에 그리기에 대한 허가를 받아야만 한다. DOS에서와 같이, Windows 화면의 절대 좌표는 이젠 필요없는 상황이 되었다. Windows에 작업영역의 그리기 허가를 받은 후, Windows가 그 작업영역의 사이즈, 폰트, 색, 그 외의 GDI 속성을 결정하고 , 그리기 허가 요구를 요청 한 프로그램에 디바이스 콘텍스트(이하,DC)를 돌려주게 된다. 그 DC가 작업영역을 그릴때 통행권의 역활을 수행하게 된다.

DC에는 컬러, 폰트라는, 그래픽 속성이 포함되어 있다. 이러한 기본값은 자유롭게 변경이 가능하다. 이것은, 모든 GDI 함수를 호출 할 때마다 매번 필요한 값으로 변경해야하는 번거로움을 줄여주는 기능을 한다. DC가 Windows로부터 제공되는 기본환경과 같은 것이라고 생각할지 모르겠지만, 만약 원한다면 몇개의 디폴트값은 다음과 같이 변경이 가능하다.

그리기(출력작업)를 할 때는, DC의 핸들을 얻어야 하며, 그 방법은 여러가지 방법이 있다.

  • WM_PAINT 메세지에서는 BeginPaint 함수를 CALL 한다
  • WM_PAINT 이외의 메세지에서는 GetDC 함수를 CALL 한다
  • 독자적인 DC를 생성하려면 CreateDC 함수를 CALL 한다
또한 DC는  반드시 하나의 메시지에서만 처리되어야만 한다. 그리고 하나의 메세지에 대한 처리를 수행했으면 메세지 처리가 끝난 후에는 반드시 DC를 해제해야 한다. 즉, 하나의 메세지에 대해서 DC를 다루는 동안에, 다른곳에서  DC를 사용하면 안되며, 만약 사용하고 싶으면, 해제하고 나서, 또 다른 DC를 얻어서 사용 해야 한다.

Windows는, 작업영역의 화면복구를 위해 WM_PAINT 메세지를 작업영역을 소유하는 윈도우에 보내게 된다. Windows는 작업영역의 내용을 저장해 주지 않는다. 대신에, 작업영역의 화면복구 요구가 발생했을 때, Windows 는 그 윈도우의 메시지 큐에 WM_PAINT 메세지를 넣는다. 이것은 , 작업영역을 실제로 복구해야할 책임이 자신에게 있다는 것이며, 윈도우 프로시저에서 WM_PAINT 메세지를 처리하는 곳에 화면복구에 대한 코드를 작성해야한다.

여기서 한가지 주의해야할 것이 있다. 그것은 바로,무효영역이다. Windows는, 화면을 복구할 필요가 있는 작업영역의 최소 사각구역을 무효영역이라고 부른다. Windows가, 작업영역의 무효영역을 감지하게 되면, 해당 윈도우에 WM_PAINT 메세지를 보내게 되어 있다. WM_PAINT 메세지를 받은 윈도우는, 무효영역에 대한 구조체를 받게되고, 구조체에는 무효영역의 좌표값이 들어 있다. 무효영역을 유효영역으로 만들기 위해서, WM_PAINT 메세지에서는 BeginPaint 함수를 호출한다. 만약, WM_PAINT 메세지를 처리하지 않는다면, DefWindowProc 함수가, ValidateRect 함수를 자동으로 호출해서 처리해 주게 된다.

다음은, WM_PAINT 메세지에 대한 처리의 개요다.

  • BeginPaint 함수로 DC 핸들 획득
  • 작업영역 화면복구
  • EndPaint 함수로 DC핸들 해제

명시적으로 무효영역을 유효하게 할 필요가 없다는 것에 주의하라. BeginPaint 함수를 호출하면, 자동으로 유효하게 된다. BeginPaint 함수와 EndPaint 함수와의 사이에는, 작업영역을 처리하기 위한 어떤 GDI 함수라도 호출이 가능하다. 그런 함수들은 대부분 DC 핸들을 인수로 갖는다. 이 DC핸들은 BeginPaint의 반환값으로 EAX레지스터에 들어있다.

Content:

그럼,작업영역의 중앙에, "Win32 assembly is great and easy! "라는 문자열을 출력해보자.

.386
.model flat, stdcall
option casemap:none

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

include \masm32\include\windows.inc
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
.DATA
ClassName db "SimpleWinClass", 0
AppName db "Our First Window", 0
OurText db "Win32 assembly is great and easy! ", 0

.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
   LOCAL rect:RECT
   .IF uMsg==WM_DESTROY
       invoke PostQuitMessage, NULL
   .ELSEIF uMsg==WM_PAINT
       invoke BeginPaint, hWnd, ADDR ps
       mov   hdc, eax
       invoke GetClientRect, hWnd, ADDR rect
       invoke DrawText, hdc, ADDR OurText,-1, ADDR rect, \
               DT_SINGLELINE or DT_CENTER or DT_VCENTER
       invoke EndPaint, hWnd, ADDR ps
   .ELSE
       invoke DefWindowProc, hWnd, uMsg, wParam, lParam
       ret
   .ENDIF
   xor  eax, eax
   ret
WndProc endp
end start

Analysis:

프로그램의 대부분은 3장에서 설명한 것과 같으므로, 변경된 곳만 살펴보도록 하자.

LOCAL hdc:HDC
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT

이 부분은, WM_PAINT 메세지에서 사용하는 GDI 함수가 사용하는 로컬 변수들에 대한 선언이다. hdc는, BeginPaint 함수의 반환값인 DC 핸들을, ps는, PAINTSTRUCT 구조체를 저장한다. 보통, 이 구조체안의 변수는 자주 사용하지는 않는다. BeginPaint 함수에 그대로 전달하면 되며, Windows가 적절한 값을 설정해준다. 그리고, 작업영역의 화면복구가 끝나면, 그 ps 를 EndPaint 함수에 넘겨주고 종료 된다. rect는 RECT 구조체로 아래와 같이 정의되어 있다.

RECT Struct
   left     LONG ?
   top      LONG ?
   right    LONG ?
   bottom   LONG ?
RECT ends

lefttop는 영역의의 좌상좌표, rightbottom는 우하의 좌표값이다. 주의 할 점은 좌표의 원점이 좌측 상단이라는 것이다. 일반적인 증가방향이 반대라는 뜻이다.
즉, y=10 는 y=0 보다 아래쪽에 위치한다.

invoke BeginPaint, hWnd, ADDR ps
mov    hdc, eax
invoke GetClientRect, hWnd, ADDR rect
invoke DrawText, hdc, ADDR OurText,-1, ADDR rect, DT_SINGLELINE or DT_CENTER or DT_VCENTER
invoke EndPaint, hWnd, ADDR ps

WM_PAINT 메세지 처리는, 그리기 처리를 하고 싶은 윈도우 핸들과 현재 초기화되지 않은 PAINTSTRUCT 구조체를 인수로 해서 BeginPaint 함수를 호출 한다. 함수가 성공적으로 수행되면, eax 레지스터에 DC 핸들이 저장된다. 다음으로, GetClientRect 함수를 호출 해서 작업영역의 좌표값을 얻고, rect 구조체를 DrawText 함수의 인수로 넘겨주게 된다.DrawText 함수의 프로토타입은 다음과 같다.

DrawText proto hdc:HDC, lpString:DWORD, nCount:DWORD, lpRect:DWORD, uFormat:DWORD

DrawText 함수는 문자열을 출력하는 하이레벨의 API 함수로서, 문자열을 처리하는 복잡한 구조를 갖는다. 그렇기 때문에, 문자열 처리에 대한 세심한 작업을 수행 할 수 있다. 이것과 대조적인, TextOut 함수는 다음장에서 다룬다. DrawText 함수는 작업영역에 딱 맞도록 문자열을 조정해서 , 현재 설정되어 있는 폰트나 컬러, 배경색을 기본으로 해서 문자열을 표시한다. DC사용이 끝나면 문자열의 높이값을 반환한다. 이예제에서는 픽셀값이다. 인수에 대해서 자세히 알아보자.

  • hdc
    DC 핸들
  • lpString
    작업영역에 표시하고 싶은문자열의 포인터. NULL문자로 종료하는 문자열이 아닌 경우는, 인수 nCount에 문자의 갯수를 대입한다.
  • nCount
    출력문자수. NULL문자로 종료하는 문자열이면, nCount는 -1 을 대입한다. 널종료 문자열이 아니라면, 표시하고 싶은 문자수.
  • lpRect
    RECT 구조체의 포인터로서,해당영역에 문자열을 출력한다. 이것은 클리핑 영역이라고도 부른다. 즉, 이 영역외에는 출력하지 않는다.
  • uFormat
    문자열을 어떻게 출력하는지를 지정한다. 다음과 같이 3개의 값을 사용할 수 있고 "or" 연산자로 결합해서도사용 할 수 있다
    DT_SINGLELINE : 한줄로 표시한다
    DT_CENTER     : 중앙에(좌우) 표시한다
    DT_VCENTER    : 중앙에(상하) 표시한다(DT_SINGLELINE이 지정되어 있어야 한다)

작업영역의 출력이 끝나면, EndPaint 함수를 호출 해서 , DC핸들을 해제 해야 한다.
요점을 정리하면 다음과 같다.

  1. WM_PAINT 메세지를 처리할려면, BeginPaint 함수, EndPaint 함수를 호출한다 .
  2. BeginPaint 함수와 EndPaint 함수의 사이에 작업영역에 대한 처리를 한다.
  3. 다른 메세지(WM_PAINT이외)에 대해, 작업영역에 출력하고 싶다면 두가지 방법이 있다.
    • GetDC 함수와 ReleaseDC 함수를 쌍으로 호출해서 , 두함수 사이에서 출력처리를 할 수 있다
    • InvalidateRect 함수, 혹은 UpdateWindow 함수를 호출해서 작업영역 전체를 무효영역으로 해서, 강제로 WM_PAINT 메세지를 발생시켜서, WM_PAINT 메세지 처리를 수행한다

Posted by openserver