단순 윈도우 만들기
이 자습서에서 우리는 데스크톱에 완벽하게 작동하는 윈도우를 뿌려주는 Windows 프로그램을 작성 할 것입니다
이론:
Windows 프로그램은 GUI 용 API 기능들에 크게 의존합니다. 이 접근 방식은 사용자와 프로그래머 모두에게 이득을 줍니다. 사용자는 새로운 프로그램의 GUI를 탐색하는 방법을 배울 필요가 없으며 Windows 프로그램의 GUI도 비슷합니다. 프로그래머에게는 GUI 코드가 이미 테스트되고 사용할 수 있도록 준비가 되어 있다는 것입니다.
프로그래머에게 단점이 될 수 있는 것은 복잡성이 증가한다는 것입니다.
윈도우, 메뉴 또는 아이콘과 같은 GUI 객체를 생성하거나 조작하려면 프로그래머가 엄격한 레시피를 따라야 합니다.
그러나 이는 모듈(식) 프로그래밍이나 OOP 패러다임에 의해 극복 될 수 있을 것입니다.
데스크탑 하단 바탕 화면에 윈도우를 만드는 데 필요한 단계에 대한 개요를 설명합니다.
1. 프로그램의 핸들 인스턴스를 얻는다(필수)
2. 커맨드라인을 얻어낸다( 명령어 라인을 진행해야 하는 프로그램이 아닐 경우에는 필요없음)
3. 윈도우 클래스를 등록한다(MessageBox 나 다이얼로그 박스 같은 미리 정의된 윈도우 타입들을 사용하지 않는다면 필요하다)
4. 윈도우를 생성한다(필수)
5. 데스크탑 위에 윈도우를 보여준다 ( 즉시 윈도우를 보여주고 싶다면
6. 윈도우의 클라이언트 영역을 갱신 해준다.
7. 윈도우들로부터의 메시지들을 확인하면서 무한 루프에 들어간다.
8. 메시지가 도착하면, 그 윈도우가 관할 하는 특별한 함수에 의해서 작업이 되어진다.
9. 사용자가 윈도우를 닫을 때, 프로그램을 빠져나간다.
보시다시피 Windows 프로그램의 구조는 DOS 프로그램에 비해 다소 복잡합니다.
그러나 Windows의 세계는 DOS 세계와 완전히 다릅니다.
Windows 프로그램은 서로 평화롭게 공존 할 수 있어야 합니다.
그들은 보다 엄격한 규칙을 따라야 합니다.
프로그래머로서 당신은 프로그래밍 스타일과 습관에 보다 엄격 해야 합니다.
내용:
아래는 간단한 윈도우 프로그램의 소스 코드입니다.
Win32 ASM 프로그래밍에 대한 자세한 내용을 살펴보기 전에 프로그래밍을 쉽게 할 수 있는
몇 가지 유용한 점에 대해 짚고 넘어 가도록 하겠습니다.
- .asm 파일의 시작 부분에 모든 Windows 상수, 구조 및 함수 프로토타입을 하나의 include 파일에 넣고 포함 해야 합니다. 이렇게 함으로써 많은 노력과 오타를 덜어줍니다. 현재, MASM을위한 가장 완벽한 include 파일은 hutch의 페이지 또는 현재 페이지에서 다운로드 할 수 있는 hutch의 windows.inc입니다. 자신만의 상수들과 구조체 정의들을 정의 할 수 있지만 그것들을 별도의 include 파일에 넣어서 관리 하도록 해야 합니다
- includelib 지시문을 사용하여 프로그램에 사용되는 가져 오기 라이브러리를 지정합니다. 예를 들어, 프로그램이 MessageBox를 호출하는 경우 다음 행을 입력 해야 합니다:
includelib user32.lib |
asm 파일의 시작 부분에서 이 지시문은 프로그램이 해당 import 라이브러리의 함수들을 사용한다고 MASM에 알리는 역할을 한다고 보면 됩니다. 프로그램이 둘 이상의 라이브러리에서 함수를 호출하는 경우 사용하는 각 라이브러리에 대해 includelib를 추가합니다. IncludeLib 지시문을 사용하면 링크 할 때 import 라이브러리에 대해 걱정할 필요가 없습니다. / LIBPATH 링커 스위치를 사용하여 Link에 모든 lib가 어디에 있는지 알려줄 수 있습니다.
- API 함수 프로토 타입, 구조체 또는 상수를 포함 파일에 선언 할 때 대/소문자 포함 Windows 파일에 사용 된 원래 이름들을 사용하도록 합니다. 이렇게 하면 Win32 API 참조에서 몇 가지 항목을 찾을 때 많은 골칫거리를 줄일 수 있습니다.
- makefile을 사용하여 어셈블리 프로세스를 자동화하면 많은 타이핑을 줄일 수 있습니다.
.386
.model flat,stdcall
option casemap:none
include windows.inc
include user32.inc
includelib user32.lib ; calls to functions in user32.lib and kernel32.lib
include kernel32.inc
includelib 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
코드 분석
간단한 Windows 프로그램이라도 코딩이 많이 필요하다는 사실을 알고 있었을지도 모릅니다. 하지만 대부분의 코드는 하나의 소스 코드 파일에서 다른 파일로 복사 할 수있는 * 템플릿 * 코드입니다.
또는 원하는 경우 이러한 코드 중 일부를 프롤로그 및 에필로그 코드로 사용할 라이브러리로 조합 할 수 있습니다.
WinMain 함수안에 코드를 작성할 수도 있습니다.
실제로 이 과정을 거치게 만들어 주는 것이 C 컴파일러가 하는 역할 인 것입니다.
컴파일러들은 다른 관리 작업에 대해 걱정하지 않고 WinMain 코드를 작성할 수 있게 해줍니다.
유일하게 따라야 하는 것이 있다면 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
처음 세 줄은 "필수적인 내용"입니다.
.386 MASM에게 이 프로그램이 80386 명령어 세트를 사용할 것을 알립니다. .model flat, stdcall 우리 프로그램이 플랫 메모리 어드레싱 모델을 사용한다는 것을 MASM에 알려주고, 프로그램에서 stdcall 매개 변수 전달 규칙을 기본값으로 사용한다고 알려 줍니다. |
다음은 WinMain의 함수 프로토 타입입니다. 나중에 WinMain을 호출 할 것이므로 함수 프로토 타입을 먼저 정의해야 호출 할 수 있습니다.
소스 코드의 시작 부분에 windows.inc를 포함해야 합니다. 여기에는 프로그램에서 사용하는 중요한 구조체들과 및 상수들이 포함되어 있습니다. include 파일 인 windows.inc는 단순 텍스트 파일 이며, 텍스트 편집기로 열 수 있습니다.
windows.inc에는 전체 구조체들과 상수들이 (아직)가 포함되어 있지 않습니다.
여전히 허치와 나는 이에 대해서 연구를 하고 있습니다. 파일 내에 원하는 내용이 없으면 새로운 내용을 추가 할 수 있습니다.
우리 프로그램은 user32.dll (CreateWindowEx, RegisterWindowClassEx 등)과 kernel32.dll (ExitProcess)에 있는 API 함수를 호출하므로 프로그램을 두 개의 import 라이브러리에 연결 해야 합니다.
다음 으로 어떤 import 라이브러리를 내 프로그램에서 import 해야 하는지 어떻게 알 수 있을까요?
답 : 프로그램에 의해 호출 된 API 함수가 있는 곳을 알아야 합니다. 예를 들어, gdi32.dll에서 API 함수를 호출하면 gdi32.lib와 링크 해야 합니다.
이것이 MASM의 접근 방식입니다. TASM의 import 라이브러리 연결 방법은 훨씬 간단합니다.
다음과 같이 import32.lib 파일 하나만 연결 하면 됩니다 :
.DATA
ClassName db "SimpleWinClass",0
AppName db "Our First Window",0
.DATA?
hInstance HINSTANCE ?
CommandLine LPSTR ?
다음은 “DATA” 섹션들 입니다
.DATA에서, 우리는 두 개의 0 종결 문자열 (ASCIIZ 문자열)을 선언합니다 :
ClassName은 윈도우 클래스의 이름이고 AppName은 윈도우의 이름입니다.
두 변수들은 초기화 된다는 것에 주의하십시오.
.DATA?에서는 두 개의 변수가 선언되었습니다 : hInstance (프로그램의 인스턴스 핸들)와 CommandLine (프로그램의 명령 행)이 그것 인데요. 익숙하지 않은 데이터 형식 인 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 에는 모든 인스트럭션들을 포함합니다.
작성되는 모든 코드는 <시작 라벨> :과 종료 <시작 라벨> 사이에 있어야 합니다. 라벨의 이름은 중요하지 않습니다. 유일해야 하며, MASM의 명명 규칙을 위반하지 않는 한 원하는 이름을 지정할 수 있습니다.
첫 번째 인스트럭션은 GetModuleHandle을 호출하여 프로그램의 인스턴스 핸들을 검색하는 것입니다.
Win32에서는 인스턴스 핸들과 모듈 핸들이 하나이며 동일합니다.
인스턴스 핸들은 프로그램의 ID로 생각할 수 있습니다.
프로그램에서 호출해야 하는 여러 API 함수에 대한 매개 변수로 사용되므로 일반적으로 프로그램 시작 시 선언하는 것이 좋습니다.
Note: Actually under win32, instance handle is the linear address of your program in memory.
참고 : 실제로 win32에서 인스턴스 핸들은 메모리에 있는 프로그램의 선형 주소입니다.
Win32 함수에서 반환 할 때 함수의 반환 값은 eax에서 찾을 수 있습니다. 다른 모든 값은 호출에 대해 정의한 함수 매개 변수 목록에 전달 된 변수를 통해 반환됩니다.
호출하는 Win32 함수는 세그먼트 레지스터와 ebx, edi, esi 및 ebp 레지스터의 값을 거의 항상 보존합니다.
반대로 ecx와 edx는 스크래치 레지스터로 간주되며 Win32 함수에서 반환 될 때 항상 정의되지 않습니다.
Note: Don't expect the values of eax, ecx, edx to be preserved across API function calls.
참고 : eax, ecx, edx 값은 API 함수 호출을 통해 유지되지 않습니다.
결론은 API 함수를 호출 할 때 eax의 반환 값을 기대한다는 것입니다. Windows에서 함수를 호출하는 경우 함수를 반환 할 때 세그먼트 레지스터 ebx, edi, esi 및 ebp의 값을 보존하고 복원 해야 하는 규칙을 따라야 합니다. 그렇지 않으면 프로그램이 바로 충돌을 일으킬 것입니다. 여기에는 윈도우 프로 시저와 윈도우 콜백 함수들이 포함되어 있습니다.
프로그램이 명령 행을 처리하지 않으면 GetCommandLine 호출이 필요하지 않습니다. 이 예에서는 프로그램에서 필요할 때를 대비하여 호출하는 방법을 보여줍니다.
다음은 WinMain 호출입니다.
여기서 프로그램의 인스턴스 핸들, 프로그램의 이전 인스턴스의 인스턴스 핸들, 처음 보이는 명령 행 및 윈도우 상태의 네 가지 매개 변수를 받습니다. Win32에서는 이전 인스턴스가 없습니다.
각 프로그램은 자신만의 주소 공간이 있기 때문에 hPrevInst의 값은 항상 0입니다.
이는 프로그램의 모든 인스턴스가 동일한 주소 공간에서 실행되고 인스턴스가 첫 번째 인스턴스인지 여부를 알고 자하는 Win16에서 남은 잔재 같은 것으로 남은 것입니다 .
win16에서 hPrevInst가 NULL이면, 이 인스턴스가 첫 번째 인스턴스입니다.
참고 : 함수 이름을 WinMain으로 선언 할 필요는 없습니다. 사실, 이러한 선언에 대해서 어떤 이름을 정할 수 있는 자유가 있다는 것이죠. WinMain 같은 함수을 전혀 사용할 필요가 없습니다. GetCommandLine 옆의 WinMain 함수 안에 코드를 붙여 넣어도 프로그램은 잘 동작합니다.
WinMain에서 돌아 오면 eax는 종료 코드로 채워집니다. Exit 코드를 응용 프로그램을 종료시키는 ExitProcess의 매개 변수로 전달합니다.
WinMain proc Inst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD |
위의 코드는 WinMain의 함수 선언입니다. PROC 지시문 뒤에 오는 parameter : type 쌍을 주목하십시오. 이 변수는 WinMain이 호출자로부터 받는 매개 변수입니다. 이러한 매개 변수는 스택 조작 대신 이름으로 참조 할 수 있습니다. 또한 MASM은 함수에 대한 프롤로그 및 에필로그 코드를 생성합니다. 따라서 우리는 함수 입력 및 종료 시 스택 프레임에 대해 신경 쓸 필요가 없습니다.
LOCAL wc:WNDCLASSEX LOCAL msg:MSG LOCAL hwnd:HWND |
LOCAL 지시문은 함수에서 사용 된 지역 변수에 대해 스택에서 메모리를 할당합니다. LOCAL 지시어는 PROC 지시문 바로 아래에 있어야합니다.
LOCAL 지시문 바로 다음에 <로컬 변수 이름> : <변수 유형>이옵니다. 따라서 LOCAL wc : WNDCLASSEX구문은 MASM에게 wc라는 변수에 대한 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
위의 라인은 개념이 매우 간단하지만 선언해야만 하는 부분입니다. 이를 가능하게 하기 위해서는 몇 줄의 인스트럭션이 필요합니다. 이 코드를 조작하는 것에 대한 개념이 바로 윈도우 클래스입니다. 여기에서 윈도우 클래스는 단지 윈도우에 대한 청사진 혹은 정의에 불과합니다. 이 클래스에서 아이콘, 커서, 그 기능을 담당하는 함수, 색상 등등과 같은 윈도우의 중요한 특성을 정의합니다. 윈도우 클래스에서는 윈도우를 만듭니다. 이것은 일종의 객체 지향 개념입니다.
동일한 특성을 갖는 둘 이상의 윈도우을 작성하려는 경우, 이러한 모든 특성을 한 곳에서만 저장하고 필요할 때 이를 참조 해야 한다는 얘기입니다. 이 개념은 정보의 중복을 피함으로써 많은 메모리를 절약합니다. Windows는 메모리 칩이 없고 대부분의 컴퓨터에 1MB의 메모리가 있는 과거에 설계되었습니다. Windows는 부족한 메모리 리소스를 사용할 때 매우 효율적 이어야 합니다. 요점은 : 자신 만의 윈도우을 정의한 경우 WNDCLASS 또는 WNDCLASSEX 구조에서 윈도우의 원하는 특성을 채우고 창을 생성하기 전에 RegisterClass 또는 RegisterClassEx를 호출해야 합니다.
윈도우을 만들려는 각 윈도우 유형마다 윈도우 클래스를 한 번만 등록 하면 됩니다.
Windows에는 단추 및 편집 상자와 같은 몇 가지 미리 정의 된 Window 클래스가 있습니다. 이러한 윈도우 (또는 컨트롤)의 경우 윈도우 클래스를 등록 할 필요 없이 미리 정의 된 클래스 이름으로 CreateWindowEx를 호출 하면 됩니다.
WNDCLASSEX에서 가장 중요한 단일 구성원은 lpfnWndProc입니다.
lpfn은 함수에 대한 포인터를 나타냅니다.
Win32에서는 새로운 FLAT 메모리 모델로 인해 "near"포인터 또는 "far"포인터가 없으며 단지 포인터로 통일 되었습니다. 하지만 이는 여전히 Win16 시대부터 남은 유물 같은 것이라고 생각하면 됩니다. 각 윈도우 클래스는 window procedure라는 함수와 연결 됩니다. 이 윈도우 프로시저는 연관된 윈도우 클래스에서 생성 된 모든 윈도우들의 메시지 처리를 담당합니다. Windows는 사용자 키보드 또는 마우스 입력과 같은 윈도우와 관련된 중요한 이벤트를 알리기 위해 윈도우 프로시저에 메시지를 보내는 것을 책임집니다. 수신 된 각 윈도우 메시지에 적절히 응답하는 것은 이 윈도우 프로시저에서 진행됩니다.
우리는 윈도우 프로시저에서 이벤트 핸들러를 작성하는 데 거의 대부분의 시간을 할애하게 됩니다.
아래 WNDCLASSEX의 각 구성원들의 설명입니다.
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으로 초기화합니다. 응용 프로그램에서 리소스 파일에서 CLASS 지시문으로 대화 상자를 생성하는 경우 WNDCLASS 구조체를 사용하여 등록합니다. 이때, 이 멤버를 DLGWINDOWEXTRA로 설정 해야만 합니다. hInstance: 모듈의 인스턴스 핸들 hIcon: 아이콘 핸들, LoadIcon 호출로 얻을 수 있습니다. hCursor: 아이콘 핸들, LoadIcon 호출로 얻을 수 있습니다 hbrBackground: 클래스로부터 생성된 윈도우들의 배경 색깔 lpszMenuName: 클래스로부터 생성된 윈도우들의 기본 메뉴 핸들 lpszClassName: 윈도우 클래스로의 이름 hIconSm: 윈도우 클래스와 관련된 작은 아이콘을 처리합니다. 이 멤버가 NULL이면, 시스템은 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에 추가 된 새 매개 변수입니다. Windows 95 및 NT 용 새 윈도우 스타일을 여기에 입력 할 수 있습니다. dwStyle에서 일반 윈도우 스타일을 지정할 수 있지만 최상위 윈도우와 같은 특수 스타일이 필요한 경우 여기에 지정 해야 합니다. 추가 윈도우 스타일을 원하지 않으면 NULL을 사용할 수 있습니다. lpClassName: (필수). 이 윈도우에 대한 템플릿으로 사용할 윈도우 클래스의 이름을 포함하는 ASCIIZ 문자열의 주소. 클래스는 사용자 고유의 등록 된 클래스 또는 사전 정의 된 윈도우 클래스가 될 수 있습니다. 위에서 설명한 것처럼 모든 윈도우들은 윈도우 클래스를 기반으로 만들어야 합니다. lpWindowName: 윈도우의 이름을 포함하는 ASCIIZ 문자열의 주소. 윈도우 제목 표시 줄에 표시됩니다. 이 매개 변수가 NULL이면 윈도우의 제목 없는 표시 줄이 됩니다. dwStyle: 윈도우의 스타일. 여기에 윈도우의 모양을 지정할 수 있습니다. NULL을 넘기는 것은 괜찮지만 시스템 메뉴 상자가 없고 최소화/최대화 버튼도 없으며, 창 닫기 버튼도 없는 윈도우가 만들어 집니다. 별로 쓸모 없는 윈도우가 만들어 진다는 거죠. 닫으려면 Alt + F4를 눌러야 합니다. 가장 일반적인 윈도우 스타일은 WS_OVERLAPPEDWINDOW입니다. 윈도우 스타일은 비트 플래그로 조합하여 만들 수 있습니다. 따라서 "or"연산자로 여러 윈도우 스타일을 결합하여 원하는 윈도우 모양을 얻을 수 있습니다. WS_OVERLAPPEDWINDOW 스타일은 실제로 이 방법으로 가장 일반적인 윈도우 스타일을 조합하여 만들어 진 것입니다. X,Y: 윈도우의 왼쪽 위 모퉁이의 좌표입니다. 일반적으로 이 값은 CW_USEDEFAULT 여야 합니다. 즉, Windows에서 바탕 화면에 윈도우의 위치를 결정 할 수 있습니다. nWidth, nHeight: 윈도우 픽셀단위의 너비와 높이. CW_USEDEFAULT로 윈도우가 적당한 너비와 높이를 가지게 설정 할 수 있습니다. hWndParent: 윈도우의 부모 윈도우에 대한 핸들 (있는 경우). 이 매개 변수는 윈도우가 다른 윈도우의 하위 (종속)인지 여부와 그렇지 않은 윈도우를 부모에게 알려줍니다. 다중 문서 인터페이스 (MDI)의 부모 - 자식 관계의 내용은 아닙니다. 하위 윈도우는 상위 윈도우의 클라이언트 영역에 바인딩 되지 않습니다. 이 관계는 특히 Windows 내부 용입니다. 상위 윈도우가 제거되면 모든 하위 윈도우가 자동으로 삭제됩니다. 정말 간단하고 할 수 있습니다. 이 예에서는 하나의 윈도우만 있기 때문에 이 매개 변수를 NULL로 지정합니다. hMenu: 윈도우 메뉴의 핸들. 클래스 메뉴를 사용하려 한다면 NULL을 지정합니다. WNDCLASSEX 구조체의 멤버 인 lpszMenuName을 다시 살펴보도록 합시다. lpszMenuName은 창 클래스의 * default * 메뉴를 지정합니다. 이 창 클래스에서 생성 된 모든 윈도우는 기본적으로 동일한 메뉴를 갖습니다. hMenu 매개 변수를 통해 특정 윈도우에 대한 * 재정의 * 메뉴를 지정하지 않는 한, hMenu는 실제로 이중 목적의 매개 변수가 됩니다. 생성하려는 윈도우가 미리 정의 된 윈도우 유형 (예 : 컨트롤) 인 경우 해당 컨트롤은 메뉴를 소유 할 수 없습니다. 대신 hMenu가 해당 컨트롤의 ID로 사용됩니다. Windows는 hMenu가 실제로 lpClassName 매개 변수를보고 메뉴 핸들인지 제어 ID인지 여부를 결정할 수 있습니다. 미리 정의 된 윈도우 클래스의 이름 인 경우 hMenu는 컨트롤 ID입니다. 그렇지 않은 경우 윈도우 메뉴에 대한 핸들입니다. hInstance: 윈도우를 생성하는 프로그램 모듈의 핸들 인스턴스 lpParam: 윈도우에 전달 된 데이터 구조의 선택적 포인터. 이것은 MDI 창에서 CLIENTCREATESTRUCT 데이터를 전달하는 데 사용됩니다. 일반적으로 이 값은 NULL로 설정됩니다. 즉, CreateWindow ()를 통해 데이터가 전달되지 않습니다. 이 창은 GetWindowLong 함수를 호출하여 이 매개 변수의 값을 검색 할 수 있습니다. |
mov hwnd,eax
invoke ShowWindow, hwnd,CmdShow
invoke UpdateWindow, hwnd
CreateWindowEx에서 성공적으로 반환되면 윈도우 핸들이 eax에 반환됩니다. 우리는 향후 사용을 위해 이 값을 보존해야 합니다. 방금 만든 창은 자동으로 표시되지 않습니다. ShowWindow를 윈도우 핸들과 창의 * 표시 상태 *를 호출하여 화면에 표출되도록 해야합니다. 다음으로 UpdateWindow를 호출하여 윈도우에 클라이언트 영역을 다시 칠하도록 지시 할 수 있습니다. 이 기능은 클라이언트 영역의 내용을 업데이트하고자 할 때 유용합니다. 이 호출은 생략 할 수 있습니다.
.WHILE TRUE
invoke GetMessage, ADDR msg,NULL,0,0
.BREAK .IF (!eax)
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.ENDW
이 시점에서 우리의 윈도우가 화면에 나타납니다. 그러나 외부로부터 입력을 받을 수는 없는 형태입니다. 그래서 우리는 관련 이벤트에 대해 * 통지 * 할 수 있는 메커니즘이 필요합니다. 이것이 메시지 루프를 통해 수행된다고 보면 됩니다. 각 모듈마다 하나의 메시지 루프 만 존재합니다. 이 메시지 루프는 GetMessage 호출로 Windows에서 오는 메시지를 계속 확인합니다.
GetMessage는 MSG 구조체에 대한 포인터를 Windows에 전달합니다. MSG 구조체는 Windows에서 모듈의 창으로 보내려는 메시지에 대한 정보로 채워집니다. GetMessage 함수는 모듈에 윈도우에 대한 메시지가 나타날 때까지 반환되지 않습니다. 그 시간 동안 Windows는 다른 프로그램을 제어 할 수 있습니다.
이것은 Win16 플랫폼의 협업 멀티 태스킹 스키마를(cooperative multitasking scheme) 만드는 것입니다.
WM_QUIT 메시지가 수신되면 GetMessage는 메시지 루프에서 루프를 종료하고 프로그램을 종료하는 경우 FALSE를 반환합니다.
TranslateMessage는 원시 키보드 입력을 받아서 메시지 대기열에 있는 새 메시지 (WM_CHAR)를 생성하는 유틸리티 함수입니다. WM_CHAR이 포함 된 메시지에는 눌려진 키의 ASCII 값이 포함되어 있으며 원시 키보드 스캔 코드보다 처리하기 쉽습니다. 프로그램이 키 입력을 처리하지 않으면 이 호출을 생략 할 수 있습니다. 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을 통해 윈도우 프로시저에 전달됩니다.
.IF uMsg==WM_DESTROY
invoke PostQuitMessage,NULL
.ELSE
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.ENDIF
xor eax,eax
ret
WndProc endp
여기에 우리가 관심을 가져야 할 중요한 부분이 있습니다. 위의 코드가 프로그밍을 위한 대부분의 로직을 설계하는 곳입니다. 각 Windows 메시지에 응답하는 코드는 윈도우 프로시저에 있습니다. 코드에서 관심 있는 메시지인지 확인하려면 Windows 메시지를 확인 해야 합니다. 관심 있는 메시지일 경우 해당 메시지에 대한 응답으로 수행 할 작업을 처리 한 다음 eax에 0을 반환하십시오. 그렇지 않은 경우 DefWindowProc을 호출 하면 됩니다. 기본 처리를 위해 받은 모든 매개 변수를 다시 이 함수에 전달해야 합니다. 이 DefWindowProc는 프로그램에서 관심이 없는 메시지를 처리하는 API 함수입니다.
응답해야 하는 유일한 메시지는 WM_DESTROY입니다. 이 메시지는 창을 닫을 때마다 윈도우 프로시저로 전송됩니다. 윈도우 프로시저가 이 메시지를 받으면 윈도우가 이미 화면에서 제거됩니다. 이것은 윈도우가 파괴되었다는 알림 일 뿐이므로 Windows로 돌아가기 위한 준비작업을 수행 해야 합니다. 이에 대한 응답으로 Windows로 돌아 가기 전에 정리 작업을 수행 할 수 있습니다. 이 상태가 되면 시스템을 빠져 나갈 수 밖에 없습니다. 사용자가 창을 닫지 못하도록 하려면 이 WM_CLOSE 메시지를 처리 해야만 합니다. 이제
하우스 키핑 작업을 수행 한 후 WM_DESTROY로 돌아가서 WMQQUIT 메시지를 해당 모듈에게 게시 할 수 있는 PostQuitMessage를 호출 해야 합니다. WM_QUIT는 GetMessage가 eax에 0 값을 반환 하도록 하며, 차례로 메시지 루프를 종료하고 Windows로 종료합니다. DestroyWindow 함수를 호출하여 자신의 윈도우 프로시저에 WM_DESTROY 메시지를 보낼 수 있습니다.
이상.
'프로그래밍' 카테고리의 다른 글
[자바]파일에서 BOM 문자 제거하기 (0) | 2022.04.10 |
---|---|
Win32 어셈블리 프로그래밍 - 4. 텍스트 그리기 (0) | 2022.04.04 |
Win32 어셈블리 프로그래밍 - 2. 메시지 박스 (0) | 2022.04.04 |
Win32 어셈블리 프로그래밍 - 1. 기초 (0) | 2022.04.04 |
윈도우 실행 파일 oxc000007b 오류 (0) | 2022.04.01 |