2007. 2. 15. 17:46
DevX Win32 Assembly Tutorial 3: A Simple Window

Tutorial 3: A Simple Window

이번장에서는, 데스크탑의 기본이 되는 윈도우를 표시하는 프로그램을 작성한다.
   소스       실행 결과   

Theory:

Windows 프로그램은, GUI의 대부분을 API 함수에 의존하고 있다. 이런 공통된 방식은 일관된 사용법을 제공하므로 여러모로 편리하다. 사용자는, 다른 프로그램에서 익힌 GUI의 조작법을 같은방식으로 사용하면 되므로, 사용법을 따로 배우지 않아도 된다. 프로그래머는 사용되는 GUI 코드가 이미 테스트되고 사용되어 지고 있으므로, 믿을 수 있는 코드를 사용할 수 있게 된다. 대신에 프로그래머는 복잡한 룰을 익혀야만 하는 수고가 따르게 된다. 메뉴 / 비트맵 / 아이콘 등의 자원을 이용하는 방법부터 메모리와 같은 모든것에 대한 룰을 익혀야하는 부담이 있다.

화면에 윈도우를 표시하기 위해서는 다음과 같은 순서를 따라야한다.

  1. 인스턴스 핸들을 획득
  2. 명령라인 인수를 획득(필요한 경우)
  3. 윈도우 클래스를 등록(MessageBox나 DialogBox가 아니라면 필요)
  4. 윈도우를 생성
  5. 데스크탑에 윈도우를 표시(표시하고 싶지 않으면 별도로 하지 않아도 된다)
  6. 윈도우의 클라이언트영역을 리프레쉬
  7. 무한루프(메시지 루프)에서 메세지를 감시한다
  8. 메세지가 오면, 처리하고 싶은 메세지에 대해서만 적절한 처리를 한다
  9. 사용자가 「닫기」버튼(×버튼)을 누르면 종료하도록 한다

보는 대로, Windows 프로그램은 DOS 프로그램에 비하면 조금 복잡하지만, Windows 프로그램의 세계는 DOS와는 완전히 다르다, Windows가 멀티태스킹 OS이므로,어떤 윈도우 프로그램이 다른 프로그램을 침해해서는 안되며, 보다 엄격한 룰을 따라야한다.

Content:

아래의 프로그램은 단순히 윈도우를 표시하는 코드다. 무식한 Win32 Assembly프로그램 설명에 들어가기 전에, 알아두면 유용한 몇가지를 소개한다.

  • Windows의 모든 상수, 구조체, 함수프로토타입이 기록되어 있는 인클루드 파일을, .asm 파일의 제일 처음 부분에 include 해야 한다. 현재, 가장 믿을수 있는 인클루드 파일은 hutch 가 제작한 windows.inc 로, 그의 홈페이지에있다. 물론, 스스로 작성한 상수정의파일을 사용 할 수는 있지만, windows.inc 에 추가하지 말고 대신에, 다른 파일에 쓰도록 하자.
  • 임포트 라이브러리를 사용할 때는, includelib 지시어를 사용하기 바란다. 예를 들어, MessageBox를 호출하고 있다면, 프로그램의 시작부분에서 아래와 같이 쓰면 좋다.
    includelib user32.lib
    includelib 지시어는, MASM에게 임포트 라이브러리에 있는 함수를 사용한다는 것을 지정한다. 만약 , 여러개의 프로그램 라이브러리를 사용하고 있다면, 각 함수를 사용하는 프로그램 라이브러리를 추가해주기만 하면 된다. 지금까지는 링커에 /LIBPATH 옵션으로 프로그램 라이브러리의 패스를 지정해서 사용하고 있었지만, 이 includelib 지시어를 사용하면 그런 링크시의 수고를 줄일 수 있다.
  • API 함수 프로토타입이나 구조체, 상수정의를 자신이 만든 파일로 선언할 때, 대소문자에 주의해야 하며, Windows.inc파일에 사용되는 이름과 같은 이름으로 해야한다. Win32 API 레퍼런스 매뉴얼에서 이름을 찾을 경우, 같은 이름을 사용하면 도움말을 쉽게 사용할 수있기때문이다.
  • 어셈블(assemble)을 자동으로 처리해 주는 makefile을 만들어 사용하면 타이핑량을 대폭적으로 줄일 수가 있다.

.386 
.model flat, stdcall 
option casemap:none 
include    \masm32\include\windows.inc 
include    \masm32\include\user32.inc 
include   \masm32\include\kernel32.inc 
includelib \masm32\lib\user32.lib            ; calls to functions in user32.lib and kernel32.lib 
includelib \masm32\lib\kernel32.lib 


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


.DATA                                                              ; initialized data 
ClassName db "SimpleWinClass", 0                         ; the name of our window class 
AppName db "Our First Window", 0                      ; the name of our window 

.DATA?                                                             ; Uninitialized data 
hInstance HINSTANCE ?                                       ; Instance handle of our program 
CommandLine LPSTR ?  

.CODE                                                              ; Here begins our code 
start: 
invoke GetModuleHandle, NULL                 ; get the instance handle of our program.  
                                                ;Under Win32, hmodule==hinstance mov hInstance, eax 
mov hInstance, eax 
invoke GetCommandLine         ; get the command line.  You don't have to call this function IF 
                                                        ; your program doesn't process the command line.  
mov CommandLine, eax 
invoke WinMain, hInstance, NULL, CommandLine, SW_SHOWDEFAULT    ; call the main function 
invoke ExitProcess, eax           ; quit our program.  The exit code is returned in eax from WinMain.  

WinMain proc hInst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD 
    LOCAL wc:WNDCLASSEX                                   ; create local variables on stack 
    LOCAL msg:MSG 
    LOCAL hwnd:HWND 

    mov   wc.cbSize, SIZEOF WNDCLASSEX                ; fill values in members of wc 
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra, NULL 
    mov   wc.cbWndExtra, NULL 
    push  hInstance 
    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                  ; register our window class 
    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, CmdShow                   ; display our window on desktop 
    invoke UpdateWindow, hwnd                               ; refresh the client area 

    .WHILE TRUE                                                      ; Enter message loop 
                invoke GetMessage, ADDR msg, NULL, 0,0 
                .BREAK .IF (! eax) 
                invoke TranslateMessage, ADDR msg 
                invoke DispatchMessage, ADDR msg 
   .ENDW 
    mov     eax, msg.wParam                                        ; return exit code in eax 
    ret 
WinMain endp 

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
    .IF uMsg==WM_DESTROY                                        ; if the user closes our window 
        invoke PostQuitMessage, NULL                           ; quit our application 
    .ELSE 
        invoke DefWindowProc, hWnd, uMsg, wParam, lParam  ; Default message processing 
        ret 
    .ENDIF 
    xor eax, eax 
    ret 
WndProc endp 

end start

Analysis:

아마,단순한 윈도우를 표시하는데 이렇게 많은 코드가 필요한가? 라고 생각할 수도 있을 것이다. 하지만, 이런 코드는 단순한 「형식」이므로, 다른 소스 파일에 그냥 복사해서 사용하면 그뿐이다. 또는, 이러한 코드중에서 중복되는 부분을 미리 어셈블해 놓고 라이브러리로 사용하는 방법도 좋은 방법중 하나이다. 그렇게되면, WinMain 함수의 필요한 부분만 작성해서 프로그램을 쉽게 작성할 수 있게 된다. 실제로 , (VC++등의) C컴파일러가 그렇게 사용하고있다(t_WinMain). 그렇기 때문에, 컴파일러를 사용하면, 그런 번거로운부분을 생각하지 않고도 쉽게 WinMain 함수를 쓸 수 있는 것이다. 다만, C컴파일러를 사용하는 부분에서, 유일한 문제는 WinMain라는 이름의 함수를 반드시 사용해야만 한다는 것이다. 이것이 없으면, C컴파일러는 프로그램의 시작부분을 찾지못하게 되어 에러를 낼 것이다. 어셈블러에는, 그런 제한이 없다. WinMain 대신에 어떤 함수명이라도 사용할 수 있고, 함수가 없어도 상관없다.

-이 정도만 알아두고, 프로그램을 설명한다.

.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
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib

처음의 3줄은 윈도우 프로그램에서 사용하는 「필수코드」이다. (오버헤드)
.386 이란 지시어는, 프로그램에서 80386CPU 의 명령어를 사용한다는 것을 지정하며, .model flat, stdcall 은, 플랫메모리모델을 사용하는 것과, stdcall 방식의 함수의 호출 규약을 디폴트로 사용한다는것을 선언하고 있다.

다음은, WinMain 함수의 프로토타입 선언부분이다. 뒷 부분에서 WinMain 함수를 호출하고 있으므로, 호출하는 행보다 이전에 함수 프로토타입을 선언해 두어야만 한다.(전방참조)

소스코드의 처음에, windows.inc 를 인클루드해 두어야만 한다. windows.inc 에는, 프로그램에서 사용하는 각종 중요한 구조체나 상수정의가 선언되있다. windows.inc 는 단순한 텍스트 파일이므로, 언제든지 편집할 수 있지만, Windows에서 사용되는 모든 구조체나 상수가 선언되어 있는 것은 아니다. 그러므로, 가장 최신의 Windows.inc파일을 유지하기 바란다. 누락된것은 추가해도 된다.

또한 프로그램은, user32.dll 에 존재하는 API 함수(CreateWindowEx 나 RegisterWindowClassEx 등)나, kernel32.dll 에 존재하는 API 함수(ExitProcess)를 호출하고 있다. 그렇기 때문에, 이들 2개의 임포트 라이브러리를 포함 해야 한다.(includelib 명령어)

그런데, 어느 임포트 라이브러리를 인클루드해야할지는 어떻게 알 수 있을까? 이것은 MSDN이나 도움말에서 참조할 수 있고, 대부분의 경우는 상기와 같이 하면 된다.
프로그램에서 호출하는 API 함수가 어느 라이브러리에 존재하는지를 알아야만 한다. 예를 들어, gdi32.dll 에 있는 API 함수를 호출한다면, gdi32.lib 를 인클루드 해야만 한다.

이것이 MASM으로 윈도우 프로그래밍을 할려면 꼭 거쳐야하는 시작부분의 오버헤드이다.

.DATA
   ClassName db "SimpleWinClass", 0
   AppName db "Our First Window", 0

.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ? 

다음은,"DATA"섹션이다.

.DATA 섹션에는 2개의 문자열을 선언하고 있다. 하나는 윈도우 클래스명이고 다른 하나는 어플리케이션명이다. 2개의 변수는 미리 초기화되어 있다는 것에 주의하기 바란다.(정적데이터)

.DATA? 섹션에서는 2개의 변수를 선언하고 있다. hInstance(프로그램의 인스턴스 핸들)와 커멘드 라인 인수이다. HINSTANCE와 LPSTR 이란 것으로 보아 표준 데이터형이 아니라 실제론 DWORD 로, windows.inc 에 선언되어 있다. .DATA? 섹션에 있는 변수는 초기화되지 않는 변수만 존재한다는 것을 주의 하기 바란다. 즉, 실행시에 궂이 값을 초기화 하지 않아도 되는 것들이 이부분에 존재한다. 사용은 할 것들이지만 메모리영역만 확보하는 자료들로 구성되어있다는 얘기이다.주로 실행시에 얻어지는 데이터들이다.(동적데이터)

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

.CODE 섹션은 실제 코드가 존재하는 부분이다. 실제 코드는 <starting label>: 과 end<starting label> 사이에 존재해야하며,, 레이블명은 아무 이름이나 사용 할 수 있다. 이 경우, label은 프로그램에서 유일해야 하며 MASM의 명명 규칙에 어긋나서는 안 된다.

이 프로그램에서는, 프로그램의 인스턴스 핸들을 구하는 GetModuleHandle 함수를 가장 먼저 호출하고 있다. Win32 에서는, 인스턴스 핸들과 모듈 핸들은 같은 것이다. 인스턴스 핸들은 프로그램의 ID와 같은 것이라고 생각할 수 있고, 몇가지의 API 함수에서 인스턴스 핸들을 인수로 취하는 것이 있기때문에, 프로그램 시작부분에서 이 핸들을 얻는것이다.

※   실제로, 인스턴스 핸들은, 프로그램의 시작주소이다.

Win32 함수를 호출하게 되면 함수의 반환값이 존재한다. 이 반환값은 대부분 eax 레지스터에 값이 들어있게 된다. 다른 모든 값은 함수 호출시의 인수로 설정한 변수를 통해서 반환 된다.프로그래머가 호출하는 Win32 함수는 대부분, 세그먼트(segment) 레지스터인, ebx, edi, esi, 그리고 ebp 레지스터를 내부적으로 사용하고 있다. 반대로, ecx 나 ebx 레지스터의 값은 쓰레기값으로 채워져있다고 생각하면 된다,일단 Win32 함수가 호출된 후에는 이 레지스터의 값은 호출하기 전 상태를 유지하고 있는 경우가 거의 없다.

※   API 함수를 호출한 후에, eax, ecx, edx 레지스터의 값이 그대로 있다는 것을 기대하면 안 된다

맨 밑의 행(invoke ExitProcess, eax)은, API 함수를 호출 할 경우에, eax 레지스터에 반환값이 있다고 가정한다. 만약에, 프로그램이 Windows 로부터 호출되는 (CALLBACK함수) 함수라면 이 법칙을 반드시 따라야 한다. 세그먼트(segment) 레지스터 ebx, edi, esi, ebp 레지스터의 값을 반드시 백업/복원해야 한다. 그렇지 않으면, 프로그램은 곧바로 크래쉬 하게 될 것이다. 이는 윈도우 프로시저나, 콜백함수에서도 같다.

GetComandLine 함수는 반드시 필요한 것이 아니고, 커맨드라인 인수를 사용하지 않는다면 없어도 상관없다.예제 에서는, 함수의 사용법을 설명하기 위해 사용하고 있다.

다음은, WinMain 함수의 호출이다. 4개의 인수가 있다. 이들 인수는 다음과 같다

  • 프로그램의 인스턴스 핸들
  • 이전프로그램의 인스턴스의 핸들
  • 커맨드라인 인수
  • 실행시의 윈도우 표시 파라미터

Win32에서는, 이전프로그램 인스턴스가 존재하지 않기 때문에, hPrevInst 는 항상 0 이다. 이것은 Win16 과의 호환성을 유지하기 위해 존재하는 것 일뿐이다. Win16 에는 모든 프로그램은 같은 주소공간에서 실행되기 때문에, 프로그램이 실행될 때, 자신이 제일 처음으로 실행된 것인지를 알 필요가 있기 때문에 존재하는 것이다. 만약 NULL 이면 프로그램이 처음 실행한 것이라 간주 하게 된다.

 

※  반드시 WinMain이라는 함수명으로 선언할 필요는 없고, 자유롭게 이름을 사용할 수가 있다. 한번 더 말하지만, WinMain을 사용하는 것조차 필요하지 않다, 예를 들면,WinMain 함수를 호출하고 있는 라인을 지워버리고 그부분에 WinMain 함수의 내용을 붙여넣으면 같은 동작을 한다.(프로그래머에게 이해를 돕기위해 존재하는 것이다)

WinMain 함수의 반환값은, eax 레지스터에 들어있게 된다. ExitProcess 함수로 프로그램을 종료하는 것과 동시에, 이 값을 프로그램의 리턴값으로 반환하게된다.

WinMain proc Inst:HINSTANCE, hPrevInst:HINSTANCE, CmdLine:LPSTR, CmdShow:DWORD

상기의 행은 WinMain 함수의 선언부분이다. 주의 해야 할 것은, PROC 지시어에 뒤에 있는인수와 형태이다. 이런 인수는 WinMain 함수의 호출측에서 스택을 처리하게 되어 있지만, 인수는 스택을 조작해서 엑세스 할 수 있다. 거기다가, MASM은 WinMain 함수의 이런 스택 처리부분을 자동으로 처리해주므로,스택 프레임에 대해, 걱정할 필요는 없다.

LOCAL wc:WNDCLASSEX
LOCAL msg:MSG
LOCAL hwnd:HWND

LOCAL 지시어는 함수가 사용하는 지역변수의 메모리영역을 스택에 확보한다. PROC지시어 바로 뒤에, LOCAL지시어를 일괄적으로 사용해서 선언하고 있다. LOCAL 지시어는, <the name of local variable>:<variable type>과 같은 형식을 취한다. 예를 들어,wc:WNDCLASSEX 라는 것은, 스택영역에, 구조체 「WNDCLASSEX」를 확보하고 , 그 변수명을 「wc」라고 붙이는 것이다. 이렇게 함으로서, 나중에, 귀찮은 스택처리를 하지않고도 변수를 엑세스할 수가 있다. 이것은 정말로 편리한 부분이다. 대신 주의할 점이 있는데, 로컬변수는 함수를 빠져나간 후에는 자동적으로 소멸되기 때문에 다시 엑세스할 수 없게 된다. 또한, 로컬변수는 자동으로 초기화를 수행하지 않는다. 즉, LOCAL지시어 뒤에, 이러한 로컬 변수에 값을 수동으로 설정해 주어야 해야 한다.(초기화처리)

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   hInstance
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

위의 코드는,복잡한 일을 수행하는것 같지만 실제로 아주 단순하다. 윈도우클래스를 등록하는 부분만 있는것이다. 윈도우클래스를 등록하는 것은, 단순한 윈도우의 설정을 등록하는 것에 불과하다, 어떤 아이콘을 사용하는지, 어떤 커서를 표시하는지, 어느 함수가 메세지를 처리하는지에 대한 설정 하고 있다. 그리고, 이런 설정을 기본으로 한 윈도우클래스를 이용해서, 윈도우를 화면에 표시하는 것이다.

이런 방법은, 객체 지향 프로그래밍의 장점중 하나이며, 만약 , 같은 설정의 윈도우클래스로부터 2개 이상의 윈도우를 표시하고 싶다면, 등록된 클래스명만 기술하면 그만인 것이다. 이런 방법은, 복수개의 클래스 설정으로 인한 메모리의 낭비를 줄일 수가 있다. 생각해 보자, Windows 가 설계되는 무렵에, 메모리는 매우 고가여서, 대부분의 컴퓨터는 1 MB정도의 메모리만 가지고 있었다. 그렇기 때문에, Windows 는 부족한 메모리에서도 제대로 동작해야 했던 것이다.

윈도우를 표시하고 싶으면, 먼저, WNDCLASS 혹은, WNDCLASSEX 구조체를 설정하고, RegisterClass 혹은 RegisterClassEx 함수로 윈도우클래스를 등록하며, 등록은 한번만 수행하면 된다.

Windows 에는 몇개의 윈도우클래스가 이미 등록되어 있다. 예를 들면 버튼이나, 에디트박스 등이 그것이다. 이런 윈도우(컨트롤)는 따로 등록할 필요가 없고, 사용하고 싶다면, 이런 이미 등록된 이름을 클래스명으로 해서 CreateWindowsEx 함수를 호출 하기만 하면된다.

WNDCLASSEX 구조체의 멤버 변수중, 가장 중요한 것은 lpfnWndProc 이다. lpfn 라고 하는 것은, 함수의 롱포인터라는 의미이다. Win32 에서는, 메모리모델을 플랫메모리모델만 사용하기 때문에, near포인터나 far포인터라는 것은 의미가 없어졌다. 그러나, 이부분 또한, Win16 와의 호환성 때문에 남아있는 것이다. 윈도우즈는 윈도우와 유저 사이에서 어떠한 처리를 하지만, 윈도우클래스는, 그 클래스를 기반으로 생성된 윈도우의 메세지 처리를 담당하는 함수(윈도프로시져)의 포인터를 가지고 있어야만 하는 것이다.

윈도우프로시져는, 해당 윈도우로 보내지는 모든 메세지를 처리하는 역활을 담당하고 있다. 유저가 키보드를 누른다든가, 마우스를 클릭한다면, 윈도우즈는 그런 행위를 메세지로 변환해서 해당 윈도프로시져로 보내주게 되는 것이다. Windows로부터 보내진 메세지에 대해서, 정확하게 어떻게 처리할지는,윈도프로시져에서 처리하기 나름이다. 아마,대부분의 프로그램에서는, 윈도프로시져를 작성하는 것이 프로그래밍의 대부분을 차지하게 될 것이다.

다음은 윈도우 클래스 구조체에 대한 정의이다.

WNDCLASSEX STRUCT DWORD
 cbSize           DWORD     ?
 style            DWORD     ?
 lpfnWndProc      DWORD     ?
 cbClsExtra       DWORD     ?
 cbWndExtra       DWORD     ?
 hInstance        DWORD     ?
 hIcon            DWORD     ?
 hCursor          DWORD     ?
 hbrBackground    DWORD     ?
 lpszMenuName     DWORD     ?
 lpszClassName    DWORD     ?
 hIconSm          DWORD     ?
WNDCLASSEX ENDS

  • cbSize
    WNDCLASSEX 구조체의 사이즈로서, 몇 바이트인지를 나타내며, SIZEOF 연산자로 크기를 구한다.
  • style
    이 윈도우 클래스로부터 생성되는 윈도우의 스타일로서, 「or」연산자를 이용해서, 여러 가지 스타일을 혼합해서 사용 할 수 있다.
  • lpfnWndProc
    윈도우 프로시져의 포인터.
  • cbClsExtra
    윈도우클래스 구조체와는 별도로 몇바이트의 여분메모리를 확보하는지를 기술한다. 운영체제는 이부분에서 선언된 영역만큼 메모리를 확보하고, 0 으로 초기화한다. 이 부분에, 각각의 윈도우클래스에 고유한 데이터를 사용할 수가 있다.
  • cbWndExtra
    생성한 윈도우와는 별도로 몇바이트의 여분메모리를 확보하는지를 기술한다. 위에서와 같이 0 으로 초기화되고 만약, WNDCLASS 구조체를 사용해서,대화상자를 생성했다면, 이 값은 DLGWINDOWEXTRA 로 해야한다.
  • hInstance
    프로그램의 인스턴스핸들.(모듈핸들)
  • hIcon
    아이콘 핸들로, 이 핸들로 아이콘을 로드한다.
  • hCursor
    커서 핸들로, 이 핸들로 커서를 로드한다.
  • hbrBackground
    윈도우의 배경색. (배경을 칠할 브러쉬의 핸들)
  • lpszMenuName
    디폴트 메뉴 핸들.
  • lpszClassName
    윈도우 클래스명.
  • hIconSm
    작은 아이콘 핸들로, 이것이 NULL 일 경우, Windows는 이 윈도우 클래스로부터 윈도우를 생성할 때에, hIcon 핸들로부터 구한 아이콘의 사이즈를 작게 해서 사용하게 된다.

invoke CreateWindowEx,       \
        NULL,                \
        ADDR ClassName,      \
        ADDR AppName,        \
        WS_OVERLAPPEDWINDOW, \
        CW_USEDEFAULT,       \
        CW_USEDEFAULT,       \
        CW_USEDEFAULT,       \
        CW_USEDEFAULT,       \
        NULL,                \
        NULL,                \
        hInst,               \
        NULL

윈도우클래스가 등록되면, CreateWindowEx 함수로 그 윈도우 클래스에서 설정한 설정대로의 윈도우를 생성할 수 있다. 그런데 , 이 함수는, 12개의 인수가 있다.

CreateWindowExA proto \
  dwExStyle:DWORD,    \
  lpClassName:DWORD,  \
  lpWindowName:DWORD, \
  dwStyle:DWORD,      \
  X:DWORD,            \
  Y:DWORD,            \
  nWidth:DWORD,       \
  nHeight:DWORD,      \
  hWndParent:DWORD,   \
  hMenu:DWORD,        \
  hInstance:DWORD,    \
  lpParam:DWORD

그럼, 각각의 인수에 대해서 알아보자.

  • dwExStyle
    확장윈도우스타일. CreateWindow 함수로부터 새롭게 추가된 새로운 파라미터. Windows95 나 NT 용으로 새로운 윈도우스타일의 파라미터로 설정 할 수 있다 대부분의 윈도우 스타일은 dwStyle 로 설정 하지만, 비활성화 상태에서도 맨 앞에 표시되는 스타일(topmost)과 같은것을 사용할려면, 몇개의 특수한 스타일을 설정해서 사용해야 한다. 확장 스타일이 필요하지 않다면, NULL로 설정하면 된다.
  • lpClassName(필수)
    윈도우 클래스명(NULL종료문자열)의 포인터.프로그래머가 등록한 클래스이거나, 미리 정의된 클래스명이여야 한다. 위에서 설명한 바와 같이, 모든 윈도우는 윈도우클래스로부터 생성되게 되어 있다.
  • lpWindowName
    윈도우캡션의 포인터로서 윈도우 타이틀에 표시되는 문자열이 된다. 만약 NULL로 설정하면, 타이틀은 없게된다.
  • dwStyle
    윈도우 스타일을 나타낸다. NULL도 가능하지만, 그런 경우 표시되는 윈도우는, 시스템 메뉴, 최소화, 최대화 버튼, 닫기 버튼이 없게 되므로, 그런 윈도우는 아무런 쓸모가 없게될것이다. 하지만, 종료할려고 할 경우, ALT+F4 버튼으로 종료할 수 있다. 대부분의 경우 사용하는 윈도우 스타일은 WS_OVERLAPPEDWINDOW 로서 , 하나의 비트 플래그로 설정하게 되어 있다. "or" 연산자를 사용해서 원하는 형태의 윈도우를 설정할 수 있다.
  • X, Y
    데스크탑에 표시할 때에, 윈도우의 좌상단의 좌표를 지정한다 보통, CW_USEDEFAULT 를 설정해서, Windows가 임의로 설정하게 한다.
  • nWidth, nHeight
    픽셀값으로서 , 윈도우의 가로, 세로폭을 지정한다 이것도 또한, CW_USEDEFAULT 로 설정해서 Windows가 임의로 설정하게 할 수 있다.
  • hWndParent
    만약에 있다면, 부모윈도우의 핸들을 설정한다 이 파라미터는, 생성된 윈도우가, 어떤 윈도우에 속해 있는 것이라면 , 어느 윈도우가 부모인지를 Windows에게 지정해주는 역활을 한다. 이것은, MDI 어플리케이션의 부모윈도우와 자식윈도우를 나타내는 관계는 아니며, 여기서 말하는 종속 관계는, Windows 의 내부적인 특징을 의미하며, 만약 부모윈도우가 닫혀지면, 모든 자식윈도우도 자동적으로 파기되는, 그런 간단한 논리이다. 이 예제에서는, 1개의 윈도우밖에 취급하지 않기 때문에, 이 파라미터는 NULL로 설정 되어 있다.
  • hMenu
    메뉴 핸들로서 윈도우클래스에서 메뉴를 사용하지 않는다면, NULL로 하면 된다. 여기서, WNDCLASSEX 구조체를 생각해 보자. WNDCLASSEX 구조체에는, lpszMenuName 라는 멤버 변수가 있었다. 이는 윈도우 클래스가 사용하는「default」의 메뉴이다. 이 윈도우 클래스로부터 작성되는 윈도우들은, lpszMenuName 에 의해, 모두 같은 메뉴를 가지고 있게 되어, hMenu 파라미터는, 디폴트로 지정된 메뉴를 나타내게 된다. 실제로, hMenu 에는, 2가지의 의미가 있다. 하나는, 설명한 대로 그냥 메뉴지만, 다른하나는, 컨트롤이라는 윈도우가 사용하게 된다. 컨트롤들은 메뉴를 사용하지 않기 때문에 hMenu 를 ID 대신으로 사용한다. Windows 는 hMenu 가 실제 메뉴 핸들인가, 컨트롤의 ID인지를, lpClassName 파라미터로 구별한다.
  • hInstance
    윈도우를 생성한 프로그램의 인스턴스핸들(모듈핸들)
  • lpParam
    WM_CREATE 메세지의 lParam 파라미터로서 반환되는, CREATESTRUCT 구조체의 포인터를 지정한다. MDI 어플리케이션의 클라이언트 윈도우를 생성하는 경우는, CLIENTCREATESTRUCT 구조체의 포인터를 지정하고, 불필요한 경우는, NULL 을 설정 한다. 이 값은, GetWindowLong 함수를 호출해서 사용할 수 있다.

mov    hwnd, eax
invoke ShowWindow, hwnd, CmdShow
invoke UpdateWindow, hwnd

CreateWindowEx 함수가 성공적으로 수행되면, 윈도우 핸들은 eax 레지스터에 저장되어 있다. 이 값은 매우 자주 사용하므로, 저장해 두어야한다. 그리고, CreateWindowEx 함수로 생성한 윈도우는, 실제로 화면에 표시되는 것이 아니고, 표시할려면 방금 리턴된 윈도우 핸들과 화면상에 어떻게 표시하는지를 지정하는 인수로 ShowWindow 함수를 호출 해야 한다. 그런 다음,UpdateWindow 함수로 생성한 윈도우의 클라이언트영역을 다시 그려줘야한다 . UpdateWindow 함수는,작업영역을 다시그리기를 할때 아주 편리하다. 그렇지만, 반드시 이함수를 호출해야하는 것은 아니다.

.WHILE TRUE
             invoke GetMessage, ADDR msg, NULL, 0,0
               .BREAK .IF (! eax)
             invoke TranslateMessage, ADDR msg
             invoke DispatchMessage, ADDR msg
.ENDW

이상태까지 진행하면 , 화면에 윈도우가 표시된다. 그러나 이상태로는 아무일도 할 수가 없으므로, Windows로부터 발생하는 메세지를 받아서 처리하게 해주어야 한다. 이것은, 메시지 루프라는 것으로서 해결된다. 각각의 프로그램들은, 1개의 메시지 루프를 가지고 있다. 메시지 루프에서는, GetMessage 함수를 호출하는 것으로써, Windows로부터 연속적으로 오는 메세지를 항상 검사하고 있다. GetMessage 함수는 Windows 에 MSG 구조체의 포인터를 넘겨주게 되어 있다. 이 MSG 구조체는, Windows가 생성한 윈도우에 보내고 싶은 메세지(정보)가 들어 있다. GetMessage 함수는, 윈도우에 대한 어떠한 메세지(정보)가 처리될 때까지, 리턴값을 반환하지 않는다. 그렇기 때문에, Windows 는 다른 프로그램으로 제어를 옮길 수가 있다. 이것이, Win32에 있어서의 선점형멀티태스킹 환경이다. GetMessage 함수는 WM_QUIT 메세지를 받게되면 FALSE를 반환해서, 메시지 루프가 종료되고, 프로그램도 종료될 것이다.

TranslateMessage 함수는 키보드의 입력을 감지하게되면, 메시지 큐에 WM_CHAR 메세지를 생성하는 처리를 한다. WM_CHAR 메세지는, ASCII코드값을 포함하고 있어서 키보드 코드를 보다 쉽게 다룰 수 있도록 해 준다. 만약, 키스트로크(keystroke)를 다루는 것과 같은 프로그램이 아니라면, TranslateMessage 함수를 사용할 필요는 없다. 있어도 크게 문제되지 않는다.

DispatchMessage 함수는 메세지를 해당 윈도우 프로시저에 전달하는 역활을 한다.

   mov    eax, msg.wParam
   ret
WinMain endp

메시지 루프가 종료하게되면, 종료 코드가 MSG 구조체의 wParam에 저장되게된다. 그 값을 eax 레지스터에 저장하고 Windows에 제어를 되돌려준다. 이 때, Windows 는 이 반환값을 사용하지는 않지만, 일단 룰을 따르는 것이 안전하므로, 그렇게 하도록 하자. (역시 오버헤드에 속한다)

WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM

이것이 프로그램의 윈도우 프로시저다. 추가로, 이 함수명을 반드시 WndProc 으로 해야할 필요는 없다(관례상). 첫번째 파라미터는, hWnd 로, 이것은 메세지를 보낸 윈도우 핸들이다. uMsg 는 메세지 파라미터이다. uMsg 는 MSG 구조체가 아니므로 주의해야한다.(그냥 메세지이다) 실제로, 이는 단순한 수치에 불과하고, 이 수치에 의해 메세지를 식별하고 있는 것이다. Windows는 몇백개의 메세지가 정의되어 있지만, 그 메세지가 몇개인지는 별로 중요하지는 않다. 어쨌든 Windows는 윈도우에서 발생한 어떠한 현상(이벤트)을 해당 윈도우에 가르쳐 주기 위해, 해당 메세지를 해당 윈도우에 보내 주는 것이다. 윈도우 프로시저는 그 메세지를 받아들여서, 메세지에 대해서 적합한 처리를 수행한다. wParam 와 lParam 은 메세지를 위한, 추가적인 정보를 전달하기 위한 것이다. 어떤 메세지는 메세지 자신에게 데이터를 추가해 보내거나 한다. 그런 데이터는 lParam 나 wParam 를 사용하게 된다. (메세지별로 사용법이 다르다)

이 윈도우 프로시져는, 아주 중요한 부분이다. 이것이 프로그램의 핵심이다. Windows에서 전달되는 각각의 메세지에 대해 처리를 수행하는것이 윈도우프로시져이다. 윈도우프로시져는, 우선, 자신이 처리를 수행하고 싶은 메세지인지를 검사한다. 만약, 직접 처리하고 싶은 메세지의 경우, 수행하고 싶은 처리코드를 기록한 후, eax 레지스터에 0 을 저장한 후 종료해야한다.(룰이다). 만약, 처리하고 싶은 메세지가 아니면, DefWindowProc 함수를 호출해서, 함수에서 받은 인수(hWnd, uMsg, wParam, lParam)를, 그대로, 그 함수에 인수로 보내주어야 한다. 그러면, 그 메세지의 디폴트 처리(최대화 버튼 등)를 Windows가 해 주게 된다.

유일하게, 디폴트 처리를 하지 않고 직접 처리 해야하는 메세지는, WM_DESTROY다. 이 메세지는 윈도우가 닫히거나 종료되었을 경우에는 반드시 해당 윈도우로보내지는 메세지다.WM_DESTROY는, 윈도우가 닫혀진 것을 알려주고 , Windows에 제어를 되돌리기 위한 준비를 하고, Windows에 제어를 되돌리기 전에, 어떠한 처리를 수행할 수 있다.

WM_DESTROY로 다시 돌아와서, 윈도우의 종료처리를 할려면, 프로그램에 WM_QUIT 메세지를 보내는 일을 하는PostQuitMessage 함수를 호출 해야 한다. WM_QUIT 메세지는, GetMessage 함수의 반환값이 0 으로 되게해서, 메시지 루프가 종료하게 하고, Windows에 제어를 반환하게 된다. 윈도프로시져에서 WM_DESTROY 메세지를 보내고 싶다면, DestroyWindow 함수를 불러도 좋다.


Posted by openserver