본문 바로가기
코스웨어/15년 스마트컨트롤러

20151112_박서연_WinAPI_1

by 알 수 없는 사용자 2015. 11. 12.
728x90
반응형

2015-11-12


*WinAPI

참고 사이트

http://www.soen.kr/


#간단한 예제로 살펴보기


#include <windows.h>

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);

//LRESULT->LONG자료형결과값,  CALLBACK->운영체제가호출한다.(컴파일시 없어진다.)

//HWAND->Window Handler, UINT->unsigned int , WPARAM->WORD형, LPARAM->LONG형

HINSTANCE g_hInst;

LPSTR IpszClass="First"; //LPSTR->Long Pointer String

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR IpszCmdParam, int nCmdShow) //APIENTRY -> EntryPoint

{

HWND hWnd;

MSG Message;

WNDCLASS WndClass;

g_hInst=hInstance;


//클래스 구조체 초기화

WndClass.cbClsExtra = 0;

WndClass.cbWndExtra = 0;

WndClass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);

WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);

WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);

WndClass.hInstance = hInstance;

WndClass.lpfnWndProc = (WNDPROC)WndProc; //메세지받았을때 동작을 설정

WndClass.lpszClassName=IpszClass; //클래스

WndClass.lpszMenuName = NULL; //메뉴

WndClass.style = CS_HREDRAW | CS_VREDRAW; //윈도우 가로세로 크기 조절

RegisterClass(&WndClass);       //클래스 등록

hWnd = CreateWindow(IpszClass,

IpszClass, 

WS_OVERLAPPEDWINDOW,    //스타일이 한꺼번에 정의되어있다.

CW_USEDEFAULT,    //윈도우의 시작좌표x 

CW_USEDEFAULT,    //윈도우의 시작좌표y

CW_USEFAUTL,          //윈도우 가로길이

CW_USEFAUTL,            //윈도우 세로길이

NULL, 

(HMENU)NULL, 

hInstance,

NULL);


ShowWindow(hWnd, nCmdShow); //2번째 인자가 화면에 보일지 안보일지 결정한다.

while(GetMessage(&Message, 0, 0, 0))

       //운영체제가 주는 메세지를 받아서 처리하는 루프(원형 큐)

{

TranslateMessage(&Message); //운영체제의 메세지 처리

DispatchMessage(&Message);

            //어플리케이션의 메세지 처리(운영체제가 처리할 메세지가 아닌놈만)

}

return Message.wParam;

}

LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)

// 실제 우리가 코딩해야할 부분

{

switch(iMessage)

{

case WM_DESTROY:

PostQuitMessage(0); //GetMessage에 0을 전달한다.

return 0;

}

return (DefWindowProc(hWnd, iMessage, wParam, lParam));


복습할 만한 내용 정리

1-3-나. 핸들에 대한 이해

핸들(handle)이란 구체적인 어떤 대상에 붙여진 번호이며 문법적으로는 32비트의 정수값이다. 도스 프로그래밍에서는 거의 유일하게 파일 핸들만이 사용되었으며 그래서 도스에서 핸들은 곧 파일 핸들을 의미하는 경우가 많았다. 그러나 윈도우즈에서는 여러 가지 종류의 핸들이 사용되고 있다. 만들어진 윈도우에는 윈도우 핸들(hWnd)을 붙여 윈도우를 번호로 관리하며 아직은 잘 모르겠지만 DC에 대해서도 핸들을 사용하고 논리적 펜, 브러시에도 핸들을 붙여 관리한다. 심지어 메모리를 할당할 때도 할당한 메모리의 번지를 취급하기보다는 메모리에 번호를 붙인 메모리 핸들을 사용한다. 왜 이렇게 핸들을 자주 사용하는가 하면 대상끼리의 구분을 위해서는 문자열보다 정수를 사용하는 것이 훨씬 더 속도가 빠르기 때문이다.

윈도우즈에서 핸들을 이렇게 많이 사용하므로 우리는 핸들의 일반적인 특성에 관해서 미리 숙지하는 것이 좋다. 핸들은 일반적으로 다음과 같은 특징이 있다.

① 일단 핸들은 정수값이며 대부분의 경우 32비트값이다. 핸들을 사용하는 목적은 오로지 구분을 위한 것이므로 핸들끼리 중복되지 않아야하며 이런 목적으로는 정수형이 가장 적합하다.

② 핸들은 운영체제가 발급해 주며 사용자는 쓰기만 하면 된다. 예를 들어 윈도우를 만들거나 파일을 열면 운영체제는 만들어진 윈도우나 열려진 파일에 핸들을 붙여준다. 사용자는 이 핸들을 잘 보관해 두었다가 해당 윈도우나 파일을 다시 참조할 때 핸들을 사용하면 된다. 사용자가 직접 핸들을 만들 경우란 없다.

③ 같은 종류의 핸들끼리는 절대로 중복된 값을 가지지 않는다. 만약 이렇게 된다면 핸들은 구분을 위해 사용할 수 없을 것이다. 물론 다른 종류의 핸들끼리는 중복된 값을 가질 수도 있다.

④ 핸들은 정수형이므로 값을 가지겠지만 그 실제값이 무엇인지는 몰라도 상관없다. 핸들은 크고 작음의 성질을 가지는 숫자가 아니라 단순한 표식일 뿐이다. 핸들형 변수를 만들어 핸들을 대입받아 쓰고 난 후에는 버리면 된다.

윈도우즈에서 핸들은 예외없이 접두어 h로 시작되며 핸들값을 저장하기 위해 별도의 데이터형까지 정의해 두고 있다. HWND, HPEN, HBRUSH, HDC 등이 핸들을 담기 위한 데이터형들이며 모두 부호없는 정수형이다.

2-2-가. WinMain

이제 소스를 차근 차근히 분석해 보되 그 전에 우선 익숙해져 있는 도스에서와의 차이점을 알아보자.

4헤더 파일

우선 제일 첫행을 보면 windows.h 하나만 인클루드되어 있다. 도스에서는 사용하는 함수에 따라 여러 개의 헤더 파일을 포함하지만 윈도우즈에서는 하나의 헤더 파일에 모든 API 함수들의 원형과 사용하는 상수들을 죄다 정의하고 있기 때문에 windows.h만 포함해 주면 된다. stdio.h나 conio.h, graphics.h 등을 포함해 줄 필요가 없다. 물론 특별한 경우에는 해당하는 헤더 파일을 포함해야 하지만 예제 수준에서는 windows.h만 포함시키면 거의 문제가 없다. windows.h 헤더 파일은 기본적인 데이터 타입, 함수 원형 등을 정의하며 그 외 필요한 헤더 파일을 포함하고 있다. 그래서 윈도우즈 프로그램의 첫 줄은 거의 항상 #include <windows.h> 로 시작된다.

4 시작점

다음으로 차이나는 점은 프로그램의 시작점인 엔트리 포인트(Entry Point)가 main 함수가 아니라 WinMain이라는 점이다. 윈도우즈 프로그램의 시작점은 main이 아닌 WinMain이다. 원형은 다음과 같다.

int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance ,LPSTR lpszCmdParam,int nCmdShow) 

도스에서의 main 함수는 인수 사용여부에 따라 여러 가지 원형이 있지만 WinMain의 원형은 위와 같이 고정되어 있다. APIENTRY 지정자는 __stdcall형 호출 규약을 사용한다는 뜻인데 일단은 없다고 생각해도 무방하다. 4개의 인수를 취하는데 각 인수의 의미는 다음과 같다.

인수의미
hInstance프로그램의 인스턴스 핸들
hPrevInstance바로 앞에 실행된 현재 프로그램의 인스턴스 핸들. 없을 경우는 NULL이 되며 WIN32에서는 항상 NULL이다. 호환성을 위해서만 존재하는 인수이므로 신경쓰지 않아도 된다.
lpCmdLine명령행으로 입력된 프로그램 인수이다. 도스의 argv인수에 해당한다.
nCmdShow프로그램이 실행될 형태이며 최소화, 보통모양 등이 전달된다.

이 중 hInstance 이외에는 잘 사용되지 않는다. 인스턴스(Instance)라는 말은 클래스가 메모리에 실제로 구현된 실체를 의미한다. 윈도우즈용 프로그램은 여러개의 프로그램이 동시에 실행되는 멀티태스킹 시스템일 뿐만 아니라 하나의 프로그램이 여러 번 실행될 수도 있다. 이때 실행되고 있는 각각의 프로그램을 프로그램 인스턴스라고 하며 간단히 줄여서 인스턴스라고 한다. 예를 들어 다음과 같이 메모장이 두번 실행되어 있다고 해 보자.

이 때 두 프로그램은 모두 메모장(Notepad.exe)이지만 운영체제는 각각 다른 메모리를 사용하는 다른 프로그램으로 인식한다. 이때 각 메모장은 서로 다른 인스턴스 핸들을 가지며 운영체제는 이 인스턴스 핸들값으로 두 개의 메모장을 서로 구별한다.

hInstance란 프로그램 자체를 일컫는 정수값이며 API 함수에서 수시로 사용된다. 그래서 이 예제에서는 WinMain의 인수로 전달된 hInstance값을 전역 변수 g_hInst에 대입해 두었다. hInstance 인수는 기억부류가 지역 변수이기 때문에 WinMain의 밖에서는 사용할 수 없기 때문이다. 이 예제에서는 g_hInst 값을 당장 사용하지 않고 있지만 앞으로의 예제에서는 이 값을 사용하게 될 것이다.

그리고 lpszClass라는 전역 문자열이 정의되어 있는데 이 문자열은 윈도우 클래스를 정의하는데 사용된다. 잠시 후에 이 문자열이 사용되는 곳에서 설명을 하도록 한다.

HINSTANCE g_hInst;
LPSTR lpszClass="First";

4 메시지 처리 함수

이 프로그램을 자세히 보면 두개의 함수만 있다. 하나는 프로그램의 시작점인 WinMain이며 나머지 하나는 WndProc이다. 도스에서는 main 함수만으로도 프로그램을 작성할 수 있지만 윈도우즈에서는 아주 특별한 경우를 제외하고는 이 두개의 함수가 모두 있어야 한다.

WinMain에서는 윈도우를 만들고 화면에 출력하기만 할 뿐이며 대부분의 일은 WndProc에서 이루어진다. WinMain은 프로그램을 시작시키기만 하며 실질적인 처리는 대부분 WndProc에서 이루어진다. WinMain의 모양은 대체로 일정하며 특별한 일을 하지 않지만 WndProc는 프로그램에 따라 천차만별로 달라진다. 그래서 소스를 분석할 때 주의깊게 봐야 할 부분은 WinMain이 아니라 WndProc이다. WinMain 바로 윗부분에 WndProc 함수의 원형이 선언되어 있다.

LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);


그리고 소스의 뒷부분에는 이 함수의 본체가 정의되어 있다.

2-2-나. 윈도우 클래스

WinMain 함수에서 하는 가장 중요한 일은 윈도우를 만드는 일이다. 윈도우가 있어야 사용자로부터 입력을 받을 수 있고 출력을 보여줄 수도 있기 때문이다. 윈도우를 만드려면 윈도우 클래스를 먼저 등록한 후 CreateWindow 함수를 호출해야 한다. 모든 윈도우는 윈도우 클래스를 기반으로 하여 만들어지며 윈도우 클래스는 만들어질 윈도우의 여러가지 특성을 정의한다. 윈도우 클래스는 windows.h에 다음과 같이 정의되어 있는 구조체이다.

typedef struct tagWNDCLASS
{
    UINT        style;
    WNDPROC     lpfnWndProc;
    int         cbClsExtra;
    int         cbWndExtra;
    HINSTANCE   hInstance;
    HICON       hIcon;
    HCURSOR     hCursor;
    HBRUSH      hbrBackground;
    LPCSTR      lpszMenuName;
    LPCSTR      lpszClassName;
} WNDCLASS;

10개나 되는 멤버를 가지고 있는데 WndClass의 각 멤버 의미는 다음과 같다. 현재 단계에서 이 내용을 일부러 암기할 필요까지는 없지만 대충 어떤 역할을 하는 멤버인지 읽어두도록 하자.

þstyle

윈도우의 스타일을 정의한다. 즉 윈도우가 어떤 형태를 가질 것인가를 지정하는 멤버이다. 이 멤버가 가질 수 있는 값은 무지하게 많지만 가장 많이 사용하는 값이 CS_HREDRAW와 CS_VREDRAW이다. 이 두 값을 OR 연산자(|)로 연결하여 사용한다. 이 값들의 의미는 윈도우의 수직(또는 수평) 크기가 변할 경우 윈도우를 다시 그린다는 뜻이다. 이밖에도 많은 값이 올 수 있다.

þlpfnWndProc

이 멤버는 윈도우의 메시지 처리 함수를 지정한다. 메시지가 발생할 때마다 여기서 지정한 함수가 호출되며 이 함수가 모든 메시지를 처리한다. 메시지 처리 함수의 이름은 물론 마음대로 정할 수 있지만 거의 WndProc으로 정해져 있는 편이다.

참고

WinMain 함수는 프로그램의 시작점이므로 도스에서 시작점이 반드시 main이어야 하는 것과 마찬가지로 이름이 고정되어 있다. 하지만 메시지 처리 함수인 WndProc는 사용자가 임의로 이름을 정할 수 있는 함수이다. 그러나 관습에 의해 이 함수의 이름은 WndProc으로 고정되어 있으므로 이 이름을 그대로 사용하는 것이 좋다. 관습이란 오랜 세월동안 많은 똑똑한 사람에 의해 가장 말썽없는 형태로 굳어진 것이므로 어겨봐야 득보다 실이 더 많다.

þcbClsExtra, cbWndExtra

일종의 예약 영역이다. 윈도우즈가 내부적으로 사용하며 아주 특수한 목적에 사용되는 여분의 공간이다. 예약 영역을 사용하지 않을 경우는 0으로 지정한다.

þhInstance

이 윈도우 클래스를 사용하는 프로그램의 번호이며 이 값은 WinMain의 인수로 전달된 hInstance값을 그대로 대입해주면 된다.

þhIcon, hCursor

이 윈도우가 사용할 마우스 커서와 최소화되었을 경우 출력될 아이콘을 지정한다. LoadCursor 함수와 LoadIcon 함수를 사용하여 지정한다. 사용자가 직접 아이콘과 커서를 만들어 사용할 수도 있지만 여기서는 윈도우즈가 디폴트로 제공하는 아이콘과 커서를 사용하고 있다. 커서는 좌측으로 기울어진 화살표 모양이며 아이콘은 와 같은 모양을 가진다.

þhbrBackground

윈도우의 배경 색상을 지정한다. 좀 더 정확하게 표현하면 윈도우의 배경 색상을 채색할 브러시를 지정하는 멤버이다. GetStockObject라는 함수를 사용하여 윈도우에서 기본적으로 제공하는 브러시를 지정한다. 지정할 수 있는 브러시에는 여러 가지 종류가 있지만 가장 일반적인 흰색 배경(WHITE_BRUSH)이 많이 사용된다.

þ lpszMenuName

이 프로그램이 사용할 메뉴를 지정한다. 메뉴는 프로그램 코드에서 만드는 것이 아니라 리소스 에디터에 의해 별도로 만들어진 후 링크시에 같이 합쳐진다. 메뉴를 사용하지 않을 경우 이 멤버에 NULL을 대입해 주면 된다.

þ lpszClassName

윈도우 클래스의 이름을 정의한다. 여기서 지정한 이름은 CreateWindow 함수에 전달되어지며 CreateWindow 함수는 윈도우 클래스에서 정의한 특성값을 참조하여 윈도우를 만든다. 윈도우 클래스의 이름은 보통 실행 파일의 이름과 일치시켜 작성하며 이 예제의 경우 lpszClass 문자열에 "First"를 대입한 후 이 문자열을 윈도우 클래스 이름으로 사용하였다.

멤버의 수가 너무 많아 한번에 다 익히기 힘들겠지만 이 중에 제일 중요한 멤버는 윈도우 클래스의 이름을 정의하는 lpszClassName과 메시지 처리 함수를 지정하는 lpfnWndProc이다. 윈도우 클래스를 정의한 후 RegisterClass 함수를 호출하여 윈도우 클래스를 등록한다.

 

ATOM RegisterClass( CONST WNDCLASS *lpWndClass);


RegisterClass 함수의 인수로 WndClass 구조체의 번지를 넘겨주면 된다. 이런 이런 특성을 가진 윈도우를 앞으로 사용하겠다는 등록 과정이다. 소스의 앞부분을 보면 WNDCLASS 구조체의 각 멤버에 값을 대입하고 윈도우 클래스를 등록하는 코드가 있다. 단순한 대입문의 연속인데 어떤 값들이 대입되었는지 잘 살펴보자.

	WNDCLASS WndClass;
	
	WndClass.cbClsExtra=0;
	WndClass.cbWndExtra=0;
	WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);
	WndClass.hCursor=LoadCursor(NULL,IDC_ARROW);
	WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);
	WndClass.hInstance=hInstance;
	WndClass.lpfnWndProc=(WNDPROC)WndProc;
	WndClass.lpszClassName=lpszClass;
	WndClass.lpszMenuName=NULL;
	WndClass.style=CS_HREDRAW | CS_VREDRAW;
	RegisterClass(&WndClass);

윈도우 클래스를 등록한 후에는 등록한 윈도우 클래스를 기본으로 윈도우를 실제 생성해야 한다. 윈도우를 생성할 때는 CreateWindow 함수를 사용한다.

HWND CreateWindow(lpszClassName, lpszWindowName, dwStyle, x, y, nWidth, nHeight, hwndParent, hmenu, hinst, lpvParam) 

보다시피 인수가 무척 많은 편이다. 윈도우즈 API 함수들은 대체로 인수가 많다. 각 인수의 의미를 알아보자.

þ lpszClassName

생성하고자 하는 윈도우의 클래스를 지정하는 문자열이다. 앞에서 정의한 WndClass구조체의 lpszClassName 멤버의 이름을 여기에 기입해 준다. 우리의 예제에서는 lpszClass 문자열에 윈도우 클래스 이름을 기억시켜 두었으므로 이 문자열을 그대로 넘겨주면 된다.

þ lpszWindowName

윈도우의 타이틀 바에 나타날 문자열이다. 여기서 지정한 문자열이 윈도우의 타이틀 바에 나타난다. 프로그래머가 마음대로 지정할 수 있는데 이 책에서는 예제의 프로젝트명(lpszClass 전역 문자열)을 타이틀 바에 나타내고 있다.

þdwStyle

만들고자 하는 윈도우의 형태를 지정하는 인수이다. 일종의 비트 필드값이며 거의 수십개를 헤아리는 매크로 상수들이 정의되어 있고 이 상수들을 OR연산자로 연결하여 윈도우의 다양한 형태를 지정한다. 윈도우가 경계선을 가질 것인가, 타이틀 바를 가질 것인가 또는 스크롤 바의 유무 등등을 세세하게 지정해 줄 수 있다. 가능한 스타일값에 관한 자세한 내용은 레퍼런스를 참조하되 WS_OVERLAPPEDWINDOW를 사용하면 가장 무난한 윈도우 설정 상태가 된다. 즉 시스템 메뉴, 최대 최소 버튼, 타이틀 바, 경계선을 가진 윈도우를 만들어 준다.

þX, Y, nWidth, nHeight

인수의 이름이 의미하듯이 윈도우의 크기와 위치를 지정하며 픽셀 단위를 사용한다. x, y좌표는 메인 윈도우의 경우는 전체 화면을 기준으로 하며 차일드 윈도우는 부모 윈도우의 좌상단을 기준으로 한다. 정수값을 바로 지정해도 되며 CW_USEDEFAULT를 사용하면 윈도우즈가 알아서 적당한 크기와 위치를 설정해 준다. 예제에서는 모두 CW_USEDEFAULT를 사용하였다.

þhWndParent

부모 윈도우가 있을 경우 부모 윈도우의 핸들을 지정해 준다. MDI 프로그램이나 팝업 윈도우는 윈도우끼리 수직적인 상하관계를 가져 부자(parent-child) 관계가 성립되는데 이 관계를 지정해 주는 인수이다. 부모 윈도우가 없을 경우는 이 값을 NULL로 지정하면 된다.

þhmenu

윈도우에서 사용할 메뉴의 핸들을 지정한다. WndClass에도 메뉴를 지정하는 멤버가 있는데 윈도우 클래스의 메뉴는 그 윈도우 클래스를 기반으로 하는 모든 윈도우에서 사용되는 반면 이 인수로 지정된 메뉴는 현재 CreateWindow 함수로 만들어지는 윈도우에서만 사용된다. 만약 WndClass에서 지정한 메뉴를 그대로 사용하려면 이 인수를 NULL로 지정하면 되며 WndClass에서 지정한 메뉴 대신 다른 메뉴를 사용하려면 이 인수에 원하는 메뉴 핸들을 주면 된다. First 예제의 경우 WndClass에도 메뉴가 지정되어 있지 않고 CreateWindow 함수에서도 메뉴를 지정하지 않았으므로 메뉴없는 프로그램이 만들어진다.

þhinst

윈도우를 만드는 주체, 즉 프로그램의 핸들을 지정한다. WinMain의 인수로 전달된 hInstance를 대입해 주면 된다.

þlpvParam

CREATESTRUCT라는 구조체의 번지이며 특수한 목적에 사용된다. 보통은 NULL값을 사용한다.

지금 당장 여기서 CreateWindow의 모든 인수에 대해 다 외우려고 할 필요까지는 없고 예제에서 어떤 값이 사용되었는가와 그 의미가 무엇인가만 대충 보고 가도록 하자. 여기서 잠깐 필자가 잔소리를 좀 하자면 API 공부를 할 때는 무시할 건 과감하게 무시하고 지나가는 요령이 필요하다. CreateWindow 함수는 API를 처음 배우는 사람에게 무척 중요하기는 하지만 그렇다고 처음부터 11개나 되는 인수들의 정확한 의미까지 속속들이 이해할 필요까지는 없다. x,y,lpszWindowName 등 쉽게 이해되는 인수들만 대충 봐두면 되지 lpvParam같은 전문적이고 어려운 인수의 의미나 dwStyle인수의 모든 스타일값에 대해 반드시 알아야 하는 것은 아니다. 물론 완벽하게 이해한다고 나쁠 것은 없겠지만 그렇게 하다가는 제풀에 지쳐 금새 흥미를 잃고 만다. 중요한 것은 이론적으로 의미가 있는 큰 줄기를 먼저 파악하는데 정성을 쏟으라는 것이다.

CreateWindow 함수는 윈도우에 관한 모든 정보를 메모리에 만든 후 윈도우 핸들을 리턴값으로 넘겨준다. 넘겨지는 윈도우 핸들은 hWnd라는 지역 변수에 저장되었다가 윈도우를 참조하는 모든 함수의 인수로 사용된다.

CreateWindow 함수로 만든 윈도우는 어디까지나 메모리상에서만 있을 뿐이며 아직까지 화면에 출력되지는 않았다. 메모리에 만들어진 윈도우를 화면으로 보이게 하려면 다음 함수를 사용해야 한다.

BOOL ShowWindow(hWnd, nCmdShow); 

hWnd 인수는 화면으로 출력하고자 하는 윈도우의 핸들이며 CreateWindow 함수가 리턴한 핸들을 그대로 넘겨주면 된다. nCmdShow는 윈도우를 화면에 출력하는 방법을 지정하며 다음과 같은 매크로 상수들이 정의되어 있다.

매크로 상수의미
SW_HIDE윈도우를 숨긴다.
SW_MINIMIZE윈도우를 최소화시키고 활성화시키지 않는다.
SW_RESTORE윈도우를 활성화시킨다.
SW_SHOW윈도우를 활성화시켜 보여준다.
SW_SHOWNORMAL윈도우를 활성화시켜 보여준다.

nCmdShow 인수에 어떤 값을 넘겨줄 것인가는 전혀 고민할 필요가 없으며 WinMain 함수의 인수로 전달된 nCmdShow를 그대로 넘겨주기만 하면 된다. 그래서 ShowWindow(hWnd,nCmdShow);와 같이 거의 호출 형식이 정해져 있는 셈이다. 설명이 좀 길어지기는 했지만 윈도우를 만들고 화면에 나타내는 코드는 다음 두 줄이다.

	hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,
		  CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
		  NULL,(HMENU)NULL,hInstance,NULL);
	ShowWindow(hWnd,nCmdShow);

여기까지 실행하면 화면에 윈도우가 출력된다. 이 후부터는 메시지 루프가 시작되며 프로그램이 사용자와 윈도우즈, 그리고 다른 프로그램과 상호 정보를 교환하며 실행된다. 여기까지 윈도우를 만드는 과정을 간단하게 정리해 보도록 하자.

이 과정은 거의 정형화된 과정이므로 계속 윈도우즈 프로그래밍을 공부할 생각이 있다면 암기할 만도 하다. 이 과정이 WinMain에서 반드시 해 주어야 할 과정이며 그 이외의 처리는 해 주어야 할 필요가 거의 없다.

2-2-다. 메시지 루프

윈도우즈를 메시지 구동 시스템(Message Driven System)이라고 하며 이 점이 도스와 가장 뚜렷한 대비를 이루는 윈도우즈의 특징이다. 도스에서는 프로그래머에 의해 미리 입력된 일련의 명령들을 순서대로 실행하는 순차적 실행방법을 사용한다. 윈도우즈는 이와 다르게 프로그램의 실행 순서가 명확하게 정해져 있지 않으며 상황에 따라 실행 순서가 달라지는 데 여기서 말하는 상황이란 바로 어떤 메시지가 주어졌는가를 말한다.

메시지란 사용자나 시스템 내부적인 동작에 의해 발생된 일체의 변화에 대한 정보를 말한다. 예를 들어 사용자가 마우스의 버튼을 눌렀다거나 키보드를 눌렀다거나 윈도우가 최소화되었다거나 하는 변화에 대한 정보들이 메시지이다. 메시지가 발생하면 프로그램에서는 메시지가 어떤 정보를 담고 있는가를 분석하여 어떤 루틴을 호출할 것인가를 결정한다. 즉 순서를 따르지 않고 주어진 메시지에 대한 반응을 정의하는 방식으로 프로그램이 실행된다.

윈도우즈 프로그램에서 메시지를 처리하는 부분을 메시지 루프라고 하며 보통 WinMain 함수의 끝에 다음과 같은 형식으로 존재한다.

while(GetMessage(&Message,0,0,0)) {
	TranslateMessage(&Message);
	DispatchMessage(&Message);
}

메시지 루프는 세 개의 함수 호출로 이루어져 있으며 전체 루프는 while문으로 싸여져 있다. 각 함수가 어떤 동작을 하는지 대충 알아보자.

BOOL GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax); 

이 함수는 시스템이 유지하는 메시지 큐에서 메시지를 읽어들인다. 읽어들인 메시지는 첫번째 인수가 지정하는 MSG 구조체에 저장된다. 이 함수는 읽어들인 메시지가 프로그램을 종료하라는 WM_QUIT일 경우 False를 리턴하며 그 외의 메시지이면 True를 리턴한다. 따라서 WM_QUIT 메시지가 읽혀질 때까지, 즉 프로그램이 종료될 때까지 전체 while 루프가 계속 실행된다. 나머지 세 개의 인수는 읽어들일 메시지의 범위를 지정하는데 잘 사용되지 않으므로 일단 무시하기로 한다.

BOOL TranslateMessage( CONST MSG *lpMsg); 

키보드 입력 메시지를 가공하여 프로그램에서 쉽게 쓸 수 있도록 해 준다. 윈도우즈는 키보드의 어떤 키가 눌러졌다거나 떨어졌을 때 키보드 메시지를 발생시키는데 이 함수는 키보드의 눌림(WM_KEYDOWN)과 떨어짐(WM_KEYUP)이 연속적으로 발생할 때 문자가 입력되었다는 메시지(WM_CHAR)를 만드는 역할을 한다. 예를 들어 A키를 누른 후 다시 A키를 떼면 A문자가 입력되었다는 메시지를 만들어 낸다.

LONG DispatchMessage( CONST MSG *lpmsg); 

시스템 메시지 큐에서 꺼낸 메시지를 프로그램의 메시지 처리 함수(WndProc)로 전달한다. 이 함수에 의해 메시지가 프로그램으로 전달되며 프로그램에서는 전달된 메시지를 점검하여 다음 동작을 결정하게 된다.

메시지 루프에서 하는 일은 메시지를 꺼내고, 필요한 경우 약간 형태를 바꾼 후 응용 프로그램으로 전달하는 것 뿐이다. 이 과정은 WM_QUIT 메시지가 전달될 때까지, 즉 프로그램이 종료될때까지 반복된다. 결국 메시지 루프가 하는 일이란 메시지 큐에서 메시지를 꺼내 메시지 처리 함수로 보내주는 것 뿐이다.

실제 메시지 처리는 별도의 메시지 처리 함수(WndProc)에서 수행한다. 메시지는 시스템의 변화에 대한 정보이며 MSG라는 구조체에 보관된다. MSG 구조체는 다음과 같이 정의되어 있다.

typedef struct tagMSG
{
    HWND        hwnd;
    UINT        message;
    WPARAM      wParam;
    LPARAM      lParam;
    DWORD       time;
    POINT       pt;
} MSG;

각 멤버의 의미는 다음과 같다.

멤버의미
hwnd메시지를 받을 윈도우 핸들이다.
message어떤 종류의 메시지인가를 나타낸다. 가장 중요한 값이다.
wParam전달된 메시지에 대한 부가적인 정보를 가진다. 어떤 의미를 가지는가는 메시지별로 다르다. 32비트값이다.
lParam전달된 메시지에 대한 부가적인 정보를 가진다. 어떤 의미를 가지는가는 메시지별로 다르다. 32비트값이다.
time메시지가 발생한 시간이다.
pt메시지가 발생했을 때의 마우스 위치이다.

message 멤버를 읽음으로써 메시지의 종류를 파악하며 message값에 따라 프로그램의 반응이 달라진다. wParam, lParam은 메시지에 대한 부가적인 정보를 가지되 메시지별로 의미가 다르다. 마치 인터럽터 루틴에서 각 레지스터의 의미가 인터럽터별로 다른 것과 마찬가지이다. GetMessage 함수는 읽은 메시지를 MSG형의 구조체에 대입해 주며 이 구조체는 DispatchMessage 함수에 의해 응용 프로그램의 메시지 처리 함수(WndProc)로 전달된다.

메시지는 실제로 하나의 정수값으로 표현되는데 메시지의 종류가 무척 많아 메시지의 번호를 일일이 암기하여 사용할 수가 없으므로 windows.h에 메시지별로 매크로 상수를 정의해 두었으며 접두어 WM_으로 시작된다. 가장 자주 사용되는 메시지의 종류를 몇 개만 보인다.

메시지의미
WM_QUIT프로그램을 끝낼 때 발생하는 메시지이다.
WM_LBUTTONDOWN마우스의 좌측 버튼을 누를 경우 발생한다.
WM_CHAR키보드로부터 문자가 입력될 때 발생한다
WM_PAINT화면을 다시 그려야 할 필요가 있을 때 발생한다.
WM_DESTROY윈도우가 메모리에서 파괴될 때 발생한다.
WM_CREATE윈도우가 처음 만들어질 때 발생한다.

메시지 루프가 종료되면 프로그램은 마지막으로 Message.wParam을 리턴하고 종료한다. 이 값은 WM_QUIT 메시지로부터 전달된 탈출 코드(exit code)이다. 도스에서 사용하는 탈출 코드와 동일한 의미를 가지며 사용되는 경우가 거의 없다.


728x90