관리 메뉴

Kim's Programming

WinApi - 분석(2/2) 본문

Programming/Windows API

WinApi - 분석(2/2)

Programmer. 2015. 8. 26. 17:38

윈도우 프로시저


메세지 처리 함수란 메세지가 발생할 때 프로그램의 반응을 처리하는 일을 하며 WinMain함수와는 별도로 WndProc이라는 이름으로 존재합니다. 윈도우 프로시저(Window Procedure)라는 뜻이지만 통상적으로 읽을 때는 윈드 프록으로 읽습니다. WndProc은 WinMain에서 호출하는 것이 아니라 운영체제에 의해 호출됩니다. WinMain내의 메시지 루프는 메세지 루프는 메세지를 메세지 처리 함수로 보내기만 할 뿐이며 WndProc은 메세지가 입력되면 운영체제에 의해 호출되어 메세지를 처리합니다. 이렇게 운영체제에 의해 호출되는 응용 프로그램 내의 콜백(Callback)함수라고 합니다. 콜백 함수라는 용어는 나중에 따로 하겠습니다.


WndProc의 인수는 모두 4개이며 MSG구조체의 앞쪽 멤버 4개와 동일합니다. hWnd는 메세지를 받을 윈도우의 핸들이며 iMessage는 어떤 메세지인가 즉  어떤 변화가 발생하였는가에 대한 정보를 가집니다. iMessage가 WM_SIZE이면 윈도우의 크기가 변경되었음은 알리고 WM_KEYDOWN이면 사용자가 키보드를 누른 것이며 WM_DESTORY이면 윈도우가 파괴되었음을 알리는 것입니다.

wParam, lParam은 iMessage의 메세지에 따른 부가적인 정보를 가집니다. 예를 들어 마우스 버튼을 눌러졌다는 WM_LBUTTONDOWN메세지가 입력되었다면 화면의 어디쯤에서 마우스 버튼이 눌러졌는가? 그떄의 키보드 상황(Shift, Ctrl, Alt)은 어떠한가에 관한 정보가 추가로 필요하며 WM_CHAR 메세지 즉 키보드로 부터 문자가 입력되었다는 메세지가 입력되었다면 어떤 문자가 입력되었는가에 관한 추가적인 정보가 필요합니다. 이런 추가 정보들이 wParam, lParam으로 전달됩니다. wParam, lParam에 실제로 어떤 값이 전달되는지는 메세지별로 다릅니다.

메세지를 처리하는 WndProc의 구조는 대체로 다음과 같은 형태를 가집니다. 다양한 종류의 메세지가 전달될 수 있는데 전달된 메세지의 종류에 따라 다중 분기하여 운영체제로부터 전달된 신호에 반응하는 형식입니다.
1
2
3
4
5
6
7
8
9
10
11
12
switch(iMessage)
{
    case Msg1:
        처리1;
        return 0;
        처리2;
        return 0;
        처리3;
        return 0;
        default:
        return DefWindowProc(...);
}
cs
Msg1 메세지가 전달되면 처리1을 한 후 리턴하고 msg2 메세지가 전달되면 처리2를 한 후 리턴합니다. case 문은 프로그램이 처리할 메세지의 수만큼 반복될 것입니다. WndProc은 메세지를 무사히 처리했으면 0으로 리턴하도록 약속되어 있습니다.

제일 끝에 있는 DefWindowProc함수는 WndProc에서 처리하지 않은 나머지 메세지에 관한 처리를 합니다. 예를 들어서 시스템 메뉴를 더블클릭하면 프로그램이 종료되는데 이런 처리는 별도로 하지 않아도 DefWindowProc함수에서 알아서 합니다. 그래서 윈도우의 이동이나 크기 변경 따위의 처리는 프로그램이 직접 할 필요없이 DefWindowProc으로 넘기기만 하면 됩니다. 이 소스의 메세지 처리함수는 다음과 같습니다.
1
2
3
4
5
6
7
8
9
10
11
12
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    switch (iMessage)
    {
    case WM_DESTROY:
        PostQuitMessage(0);    
        return 0;
        break;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
 
cs
WM_DESTORY 메세지만을 처리하고 있으며 나머지 메세지에 대해서는 DefWindowProc에 맞깁니다. WM_DESTORY 메세지는 사용자가 시스템 메뉴를 더블 클릭하거나 Alt+F4를 눌러 프로그램을 끝내려고 할 때 발생하는 메세지입니다. WndProc에서 이 메세지가 발생하면 PostQuitMessage 함수를 호출하여 WM_QUIT 메세지를 보냅니다. WM_QUIT 메세지가 입력되면 메세지 루프의 GetMessage 함수 리턴값이 FALSE가 되어 while루프를 빠져나오며 WinMain이 종료됩니다. WinMain이 종료되면 결국 프로그램이 종료되므로 WM_DESTORY는 프로그램을 끝내는 중요한 처리를 하는것입니다.

WM_DESTORY 이외의 메세지는 모두 DefWindowProc함수로 전달되며 이 함수에서 디폴트 처리를 수행합니다. 그래서 별다른 코드를 작성하지 않아도 타이틀 바를 드래그하면 윈도우가 이동하며 경계선을 드래그하면 크기가 조정되고 최소, 최대화, 종료버튼이 동작하는 것입니다. WndProc은 메세지를 처리했을 경우 반드시 0을 리턴해야 합니다. 또한 DefWindowProc 함수가 메세지를 처리했을 경우 이 함수가 리턴한 값을 WndProc 함수가 다시 리턴해야 합니다.

배경색 바꾸기


원래 만든 프로젝트의 배결색은 흰색이었습니다. 배경색이 흰색인 이유는 WndClss의 멤버 중 배경 색상을 지정하는 hbrBackground가 흰색 브러시로 지정되어 있기 떄문입니다.

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

여기서 사용된 GetStockObject 함수는 윈도우즈가 기본적으로 제공하는 브러시, 펜 등의 핸들을 구하는 함수인데 이 함수의 인수로 WHITE_BRUSH를 지정했기 떄문에 배경색을 칠하는 데 흰색 브러시가 사용되었습니다. 이 값을 BLACK_BRUSH로 변경하면 검정색이 배경색으로 사용되며 LTGRAY_BRUSH로 변경하면 옅은 회색 배경으로 만들어집니다.

LTGRAY_BRUSH로 하였을 경우 다음과 같이 나옵니다.



적접 해보면 나올 것입니다. 하지만 RED나 YELLO는 되지 않습니다. 왜냐하면 윈도우즈의 기본 브러시는 흰색, 검정색, 회색 뿐이며 원색 브러시는 제공되지 않기 떄문입니다. 윈도우즈가 제공하는 브러시 이외의 브러시를 사용하려면 다른 방법을 사용해야 하는데 소개만 하겠습니다. 다음 코드는 파란색 배경을 가지는 윈도우를 만듭니다.

WndClass.hbrBackground = CreateSolidBrush(RGB(0, 0, 255));

다음 코드는 빨간색의 기울어진 바둑판 배경을 그립니다.

WndClass.hbrBackground = CreateHatchBrush(HS_DIAGCROSS, RGB(255, 0, 0));

원하는 색상과 무늬의 브러시를 만들어 배경 브러시로 지정하기만 하면 됩니다. 원칙대로 하자면 파괴도 해야하는데 실습이므로 생략했습니다. 특정 색상을 지정하는 것보다 COLOR_WINDOW+1 시스템 색상을 지정하여 사용자가 제어판에서 설정한 윈도우 배경색을 쓰는 것이 가장 좋습니다.



커서 바꾸기


위의 윈도우 위에 마우스 커서를 위치시키면 화살표 모양의 표준 커서가 나타납니다. 이 커서가 사용되는 이유는 WndClass에서 커서를 지정하는 멤버가 다음과 같이 정의 되어있기 때문입니다.

WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);

hCursor 멤버는 윈도우가 기본적으로 사용할 커서를 지정하며 LoadCursor함수는 커서를 읽어오는 함수입니다.

HCURSOR LoadCursor(HINSTANCE hInstance, LPCTSTR lpCursorName);

첫 번째 인수 hInstance는 커서를 가지고 있는 프로그램의 인스턴스 핸들이되 윈도우즈가 제공하는 표준 커서를 사용하려면 이 인수를 NULL로 지정합니다. 두 번쨰 인수 lpCursorName은 사용하고자 하는 커서의 이름을 지정합니다. 윈도우즈가 디폴트로 제공하는 커서에는 다음과 같은 종류가 있습니다.


값 

 모양

 IDC_ARROW

 화살표 모양(보통 사용하는 모양)

 IDC_CROSS

 십자 모양

 IDC_IBEAM

 I 모양

 IDC_NO

 금지표시, 원안에 빗금

 IDC_WAIT

 모래시계 모양(윈도우 비스타 부터는 원형)

윈도우즈가 제공하는 표준 커서외에 자신이 직접 커서를 디자인해서 사용하는 방법도 있는데 이 방법은 뒤에서 알아보겠습니다. 커서를 바꿀 수 있는 것 처럼 타이틀 바의 좌 상단에 표시되는 프로그램의 아이콘도 바꿀 수 있습니다.


 값 

 모양

 IDI_ASTERISK


 IDI_ERROR


 IDI_EXCLAMATION


 IDI_QUESTION


타이틀 바의 아이콘이 지정한 모양으로 바뀝니다.(바뀐걸 캡쳐해 붙였습니다) 물론 아이콘도 운영체제가 제공하는 것이 아닌 원하는 모양으로 디자인해서 사용할 수 있습니다. 여기서 볼 수 있듯 WndClass의 멤버들을 변경하면 생성되는 윈도우의 여러 가지 속성을 원하는 대로 설정할 수 있습니다.



윈도우의 타이틀 바꾸기



위 소스를 컴파일 했을 때 타이틀 바에 "첫예제"라는 문자열이 나타내는데 이 값은 우리가 CreateWindow 함수의 두 번째 인수로 지정한 lpszClass 문자열이며 이는 또한 윈도우 클래스의 이름이기도 합니다. 타이틀 바에 나타나는 윈도우의 이름을 변경하려면 CreateWindow함수의 두 번째 인수를 원하는 문자열로 변경하면 됩니다. 진한색 부분을 변경하면 타이틀이 바뀌게 됩니다.

 hWnd = CreateWindow(lpszClass, TEXT("타이틀은여기에"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, (HMENU)NULL, hInstance, NULL);

    ShowWindow(hWnd, nCmdShow);

앞에서 설명 했듯 문자열 상수를 쓸 때는 항상 TEXT()매크로로 둘러싸야 유니코드로 쉽게 이식할 수 있습니다. 타이틀 바에 첫 예제라는 이름 말고 지정한 이름으로 표시되는 것을 확인 할 수 있습니다. 문자열이기 떄문에 한글 기호 한자 전부다 가능합니다.


실행중에 타이틀 바의 문자열을 변경하고 싶다면 wsprintf 등의 서식 조립 함수로 조립하여 사용하면 됩니다. 윈도우의 위치와 크기를 지정하는 값은 CreateWIndow의 4번째~7번째 인수 까지 이며 첫번째 이 소스에서는 나머지는 CW_USERDEFAULT를 사용하여 윈도우즈가 정해주는대로의 크기와 위치를 사용했습니다. 이값을 다음과같이 상수로 변경하면 크기와 위치를 원하는대로 정해줄 수 있습니다.

hWnd = CreateWindow(lpszClass,lpszClass, WS_OVERLAPPEDWINDOW, 300, 400, 300, 200, NULL, (HMENU)NULL, hInstance, NULL);

위의 소스에서는 300,400의 위치에 폭 300 너비 200의 창을 만든다는 의미 입니다. (진한부분) 꼭 틍정위치에 정해진 크기대로 윈도우를 만들어야한다면 이 인수들을 사용하여 윈도우를 만들 때 크기와 위치를 고정시킬 수 있되 그렇게 하는 경우는 드뭅니다. 왜냐하면 윈도우는 사용자가 언제든지 타이틀 바를 드래그하여 이동시킬 수 있으므로 운영체제가 화면 해상도에 맞게 적당한 위치에 표시하는 것이 더 자연스럽기 떄문입니다.



윈도우의 스타일


CreateWindow함수의 세 번째 인수 dwStyle은 윈도우의 모양과 동작 방식을 결정하는 여러 가지 속성을 지정합니다. 여러 가지 스타일값을 가지는 32비트 정수값이며 이 값을 변경함에 따라 다양한 모양의 윈도우를 만들 수 있다. 일단 dwStyle에 사용될 수 있는 값들을 보겠습니다. 이 값들을 OR연산자로 연결하여 여러 가지 속성을 지정할 수 있습니다.


 값 

 의미

 WS_CAPTION

 타이틀 바를 가집니다.

 WS_HSCROLL

 수평 스크롤 바를 가집니다.

 WS_VSCROLL

 수직 스크롤 바를 가집니다.

 WS_MAXIMIZEBOX

 최대화 버튼을 가집니다.

 WS_MINIMIZEBOX

 최소화 버튼을 가집니다.

 WS_SYSMENU

 시스템 메뉴를 가집니다.

 WS_THICKFRAME

 크기를 조정할 수 있는 경계선을 가집니다.

이 외에도 더 많은 속성값들이 있지만 이 값만 사용하여 변경해 보겠습니다. 우선 위에서 사용했던 WS_OVERLAPPEDWINDOW는 다음과 같이 정의 되어 있습니다.

1
#define WS_OVERLAPPEDWINDOW (WS_OVERLAPPED | WS_CAPTION | W_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX)
cs

여기서 WS_OVERLAPPED는 실제로 0이므로 아무런 의미가 없으며 타이틀 바, 시스템 메뉴, 크기 조절이 가능한 경계선, 최소, 최대 버튼 등의 스타일이 한꺼번에 정의되어 있습니다. 즉 WS_OVERLAPPEDWINDOW 스타일을 사용하면 가장 평범한 모양의 윈도우가 만들어집니다. CreatWindow의 dwStyle인수를 다음과 같이 변경합니다.

1
WS_CAPTION | W_SYSMENU 
cs

두개의 스타일만 남기고 나머지 스타일을 사용하면 다음과 같은 윈도우가 만들어집니다.


타이틀 바와 시스템 메뉴만 있을 뿐 최대, 최소화 버튼이 없습니다. 또한 윈도우 크기조정 경계선도 없어서 크기 변경도 되지 않습니다. 여기서 WS_MAXIMIZEBOX, WS_MINIMIZEBOX 스타일을 차례대로 추가 지정하면 타이틀 바에 최대화 버튼, 최소화 버튼이 생깁니다.

또 다른 걸로 바꿔서 보겠습니다.

1
WS_OVERLAPPEDWINDOW | WS_VSCROLL
cs

이렇게 변경한후 다시 프로그램을 실행하게되면 다음과 같이 수직 스크롤 바가 달린 윈도우가 만들어집니다. 수평 스크롤을 달고 싶으면 WS_HSCROLL을 추가로 주면 됩니다.


다음은 윈도우를 마우스로 왼쪽 클릭했을때 소리가 나게 하는 것입니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM lParam)
{
    switch (iMessage)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;
        break;
    case WM_LBUTTONDOWN:
        MessageBeep(0);
        return 0;
    }
    return(DefWindowProc(hWnd, iMessage, wParam, lParam));
}
cs

다음 포스팅부터는 이와 같은 출력에 대해서 작성하겠습니다.


'Programming > Windows API' 카테고리의 다른 글

WinApi - 출력(2/2)  (0) 2015.08.29
WinApi - 출력(1/2)  (0) 2015.08.27
WinApi - 분석(1/2)  (1) 2015.08.26
WinAPI - 첫번째 시작 - 프로젝트 만들기  (0) 2015.08.25
WinAPI - 시작하기전에 알고 갑시다.  (0) 2015.08.24