간단한 예제를 만들 예정이다.
#include <stdio.h>
#include <conio.h>
void main()
{
clrsr();
getch();
}
깨끗한 화면을 보여주고, 키보드가 눌러질 때까지 기다렸다가 종료하는 간단한 프로그램을 Win api로 만들어 본다.
예제 소스 코드
#include <windows.h>
LRESULT CALLBACK WndProc(HWND,UINT,WPARAM,LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass=TEXT("First");
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow)
{
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;
WndClass.lpszClassName=lpszClass;
WndClass.lpszMenuName=NULL;
WndClass.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&WndClass);
hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,NULL(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
while(GetMessage(&Message,NULL,0,0)){
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return (int)Message.wParam;
}
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM IParam)
{
switch(iMessage){
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
WinMain
헤더파일
#include <windows.h>
첫 행을 살펴보면, windows.h 하나만 인클루드되어 있다.
도스에서는 사용하는 함수의 종류에 따라 여러가지를 포함하지만, 윈도우즈에서는 하나의 헤더 파일에 모든 API 함수들의 원형과 사용하는 상수들이 정의되어 있기 때문에 windows.h 만 포함하면 된다.
windows.h 헤더 파일은 기본적인 데이터 타입, 함수 원형, 매크로 상수 등을 정의하며 그 외 윈도우즈 프로그래밍에 필요한 보조 헤더 파일을 포함하고 있다.
고로, 윈도우즈 프로그램의 첫 줄은 거의 항상 #include windows.h 로 시작한다.
시작점
int APIENTRY WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpszCmdParam,int nCmdShow)
다음으로 차이가 나는 점은 프로그램의 시작이 main 함수가 아닌 WinMain이며, 모든 윈도우즈 프로그램은 WinMain으로 실행을 시작한다.
APIENTRY 지정자는 윈도우즈의 표준 호출 규약인 _stdcall을 사용한다.
내부에는 4개의 인수를 사용하는 데, 의미는 아래와 같다.
인 수 | 의미 |
hInstance | 프로그램의 인스턴스 핸들이다. |
hPrevInstance | 바로 앞에 실행된 현재 프로그램의 인스턴스 핸들이다. 없을 경우는 NULL이 되며 Win32에서는 항상 NULL이다. 16비트와의 호환성을 위해서만 존재하는 인수이므로 신경쓰지 않아도 된다. (일종의 찌꺼기) |
lpszCmdParma | 명령행으로 입력된 프로그램 인수이다. 도스의 argv인수에 해당하며, 보통 실행 직후에 열 파일의 경로가 전달된다. |
nCmdShow | 프로그램이 실행될 형태이며 최소화, 보통 모양 등이 전달된다. |
이 중 hIstance 이외에는 잘 사용되지는 않는다.
메세지 처리 함수
LRESULT CALLBACK WndProc(HWND,WPARAM,LPARAM);
Winmain은 메인 윈도우를 만들고 화면에 윈도우를 표시하기만 할 뿐이다.
실질적인 일은 WndProc에서 이루어진다.
함수 원형에 CALLBACK이라는 매크로가 사용되었는데, 이 매크로도 APIENTRY와 마찬가지로 _stdcall로 정의되어 있다.
이후에 나오는 WINAPI라는 매크로도 _stdcall이다.
WndClass(윈도우 클래스)
WinMain 함수에서 가장 중요한 것은 메인 윈도우를 생성하는 것이다.
윈도우를 생성하려면, 윈도우 클래스를 등록한 후 CreateWindow 함수를 호출해야 한다.
클래스는 만들어질 윈도우의 여러 가지 특성을 정의하는 구조체이다.
아래는 윈도우 클래스이다.
typedef struct tagWNDCLASS
{
UINT style;
WNDPROC lpfnWndProc;
int cbClsExtra;
int cbWndExtra;
HINSTANCE hInstance;
HICON hIcon;
HCURSOR hCursor;
HBRUSH hbrBackground;
LPSTR lpszMenuName;
LPCSTR lpszClassName;
} WNDCLASS
10개의 멤버를 가지고 있는 WNDCLASS 구조체의 각 멤버 의미는 아래와 같다.
Style
윈도우 스타일을 정의한다. 윈도우가 어떤 형태를 가질 것인가를 지정하는 멤버.
이 멤버가 가질 수 있는 값은 많지만, CS_HREDRAW와 CS_VREDRAW을 OR 연산자로 연결해서 사용한다.
이 스타일은 윈도우의 수직, 수평 크기가 변할 경우 윈도우를 다시 그린다는 것을 의미한다.
lpfnWndProc
윈도우의 메세지 처리 함수를 지정한다.
물론 메세지 처리 함수를 마음대로 정할 수 있지만, 거의 WndProc으로 정해져 있는 관례이다.
cbClsExtra, cbWndExtra
일종의 예약 영역이다.
특수한 목적에 사용되는 여분의 공간이다.
hInstance
윈도우 클래스를 등록하는 프로그램의 번호이며, WinMain의 인수로 전달된 hInstance 값을 그대로 대입하면 된다.
이 윈도우 클래스를 누가 등록했는지 기억해 두었다가 프로그램 종료 시, 등록을 취소한다.
hIcon, hCursor
윈도우가 사용할 마우스 커서와 아이콘을 지정한다.
LoadCursor 함수와 LoadIcon 함수를 사용하여 커서, 아이콘을 읽어와 멤버에 대입한다.
hbrBackground
윈도우 배경 색상을 지정한다. 좀 더 명확하게 표현하면 윈도우의 배경 색상을 채색할 브러시를 지정하는 멤버이다.
GetStockObject라는 함수를 사용하여, 윈도우즈에서 기본적으로 제공하는 브러시를 지정하거나, COLOR_WINDOW같은 시스템 색상을 지정할 수도 있다.
lpszMenuName
이 프로그램이 사용할 메뉴를 지정한다.
메뉴는 프로그램 코드에서 실행중에 만드는 것이 아니라 리소스 에디터에 의해 별도로 만들어져 링크시에 합쳐진다.
별도로 메뉴를 사용하지 않을 경우 이 멤버에 NULL를 대입한다.
lpszClassName
윈도우 클래스의 이름을 문자열로 정의한다.
지정한 이름은 CreateWindow 함수에 전달되며, CreateWindow 함수는 윈도우 클래스에서 정의한 특성 값을 참조한다.
RegisterClass(클래스 등록)
이러한, 윈도우 클래스 구조체를 정의한 후 RegisterClass 함수를 호출하여 윈도우 클래스를 등록한다.
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;
WndClass.lpszClassName=lpszClass;
WndClass.lpszMenuName=NULL;
WndClass.style=CS_HREDRAW | CS_VREDRAW;
RegisterClass(&WndClass);
CreateWindow(윈도우 생성)
윈도우 클래스를 등록한 후에는 이 윈도우 클래스를 기본으로 실제 윈도우를 생성한다. 윈도우를 생성할 때는 CreateWindow 함수를 사용한다.
HWND CreateWindow(lpszClassName, lpszWindowName, dwStyle, x, y, nWidth, nHeight,
hwndParent, hmenu, hinst, lpvParam)
윈도우는 인수가 많은 편이다. 윈도우즈 API 함수들은 대체로 인수가 많으니, 각 인수에 대해 알아봐야 한다.
lpszClassName
생성하고자 하는 윈도우의 클래스를 지정하는 문자열이다.
CreateWindow 함수는 윈도우 클래스에 정의된 속성대로 윈도우를 생성한다.
앞서 등록한 WNDCLASS 구조체의 lpszClassName 멤버의 이름을 여기에 기입한다.
lpszWindowName
윈도우의 타이틀 바에 나타날 문자열이다.
dwStyle
만들고자 하는 윈도우의 형태를 지정하는 인수이다. 일종의 비트 필드값이다.
X, Y, nWidth, nHeight
윈도우의 크기와 위치를 지정하며 픽셀 단위를 사용한다.
EX) 100, 100, 640, 480으로 주면 화면의 (100,100) 위치에 폭 640 픽셀, 높이 480 픽셀 크기로 윈도우가 만들어질 것이다
hWndParent
부모 윈도우가 있을 경우 부모 윈도우의 핸들을 지정한다.
만약, 자신이 최상위 윈도우일 경우는 이 값을 NULL로 지정하는데 이렇게 하면 데스크 탑을 부모로 가져 바탕 화면 어디나 돌아다닐 수 있는 윈도우가 된다.
hmenu
윈도우에서 사용할 메뉴의 핸들을 지정한다.
윈도우 클래스에도 메뉴를 지정하는 멤버가 있는데 윈도우 클래스의 메뉴는 그 윈도우 클래스를 기반으로 하는 모든 윈도우애서 공통적으로 사용되는 반면 이 인수로 지정된 메뉴는 현재 CreateWindow 함수로 만들어지는 윈도우에서만 사용된다.
윈도우 클래스에서 지정한 메뉴를 그대로 사용하려면 이 인수를 NULL로 지정한다.
다른 메뉴를 사용하려면 이 인수에 원하는 메뉴 핸들을 지정한다.
위의 예저의 경우 윈도우 클래스에도 메뉴가 지정되어 있지 않고, CreateWindow 함수에도 메뉴를 지정하지 않아 메뉴없는 윈도우가 만들어진다.
hinst
윈도우를 만드는 주체, 프로그램의 핸들을 지정한다.
WinMain의 인수로 전달된 hInstance를 대입하면 된다.
운영체제는 누가 윈도우를 만들었는지 기억해 두었다가 프로그램이 종료될 때 파괴되지 않은 윈도우를 자동으로 파괴한다.
lpvParam
CREATESTRUCT라는 구조체의 번지이며 여러 개의 윈도우를 만들 때 각 윈도우에 고유의 파라미터를 전달하는 특수한 목적에 사용된다.
보통은 NULL 값을 사용하며 잘 사용되지 않으므로 일단 넘어간다.
ShowWindow
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_OVERAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,NULL(HMENU)NULL,hInstance,NULL);
ShowWindow(hWnd,nCmdShow);
여기까지 실행하면 화면에 윈도우를 출력하는 것 까지의 과정이라 볼 수 있다.
이제 메세지 루프를 적용하여 프로그램이 사용자와 윈도우즈, 그 이외의 프로그램과 정보를 교환하며 실행된다.
메세지루프
윈도우즈를 메세지 구동 시스템(Message Driven System)이라고 한다.
이 것이 도스와 가장 뚜렷한 차이를 보여주는 윈도우즈의 특징이다.
도스에서는 프로그래머에 의해 미리 입력된 일련의 명령들을 순서대로 싱핼하는 순차적인 과정을 거친다.
하지만, 윈도우즈에서는 프로그램의 실행 순서가 명확하게 정해져 있지 않으며 상황에 따라 실행 순서가 달라지는데 여기서 말하는 상황이란 바로 "어떤 메세지가 발생했는가"가 분기점으로 사용된다.
여기서 메세지의 의미를 이해해야한다.
메세지란 사용자나 시스템의 내부적인 동작에 의해 발생된 일체의 변화에 대한 정보이다.
EX) 사용자가 마우스 버튼을 클릭했다가 키보드를 누른다는 지, 윈도우가 최소화되었다는 지 등의 변화에 대한 정보들이 메세지이다.
윈도우즈 프로그램에서 메시지를 처리하는 부분을 메세지 루프(Message Loop)라고 하며 보통 WinMain 함수의 끝에 다음과 같은 형식으로 존재한다.
메인 윈도우를 만든 직후 WinMain은 메세지 루프를 실행한다.
While(GetMessage(&Message,NULL,0,0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
메세지 루프는 세 개의 함수 호출로 이루어져 있으며 전체 루프는 while 문으로 싸여져 있어 무한히 반복되는 구조를 가지고 있다.
아래는 메세지 루프의 세 개의 함수이다.
BOOL GetMessage
BOOL GetMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax);
이 함수는 메시지 큐에서 메세지를 읽어드린다.
메세지 큐(Message Queue)는 시스템이나 사용자로부터 발생된 메세지가 잠시 대기하는 일종의 메세지 임시 저장 영역이다.
읽어드린 메세지는 첫 번째 인수가 지정하는 MSG 구조체에 저장된다.
만약 이 함수가 읽어들인 메세지가 프로그램을 종료하라는 WM_QUIT 일 경우, FALSE를 리턴한다.
그 외의 메세지이면 TRUE를 리턴한다.
그러므로, WM_QUIT 메세지가 읽혀질 때까지(프로그램이 종료될 때 까지), while 루프가 계속 반복된다.
lpMsg를 제외한 나머지 인수는 읽어들일 메세지의 범위를 지정하는데 사용되지 않으므로, 넘어간다.
BOOL TranslateMessage
BOOL TranslateMessage(CONST MSG* lpMsg);
이 함수는 키보드 입력 메세지를 가공하여 프로그램에서 쉽게 쓸 수 있도록 한다.
윈도우즈는 키보드의 어떤 키가 눌리거나 떨어졌을 때 키보드 메세지를 발생시키는데
TranslateMessage 함수는 키보드의 눌림(WM_KEYDOWN) 메세지가 발생할 때 문자가 입력되었다는 메세지(WM_CHAR)를 만드는 역할을 한다.
LONG DispatchMessage
LONG DispatchMessage(CONST MSG* lpmsg)
이 함수는 메세지 큐에서 꺼낸 메세지를 윈도우의 메세지 처리 함수(WndProc)로 전달한다.
이 함수에 의해 메세지가 윈도우로 전달되며 프로그램(윈도우 프로시저)에서는 전달된 메세지를 점검하여 다음 동작을 결정한다.
이 함수가 메세지를 전달하면 다시 루프의 선두로 돌아가 다음 메시지를 기다린다.
메세지 루프에서 하는 일은 메세지를 꺼내고, 필요한 경우 약간의 변경을 주고 프로그램으로 전달한다.
이 과정에서 WM_QUIT 메세지가 전달될 때까지 진행한다. (프로그램이 종료될 때까지 반복한다.)
즉, 메세지 루프가 하는 일은 메세지 큐에서 메세지를 꺼내 메세지 처리 함수로 보내는 역할을 담당한다,
실제 메세지 처리는 별도의 메세지 처리 함수(Proc)에서 수행한다.
메세지 루프의 함수는 공통적으로 MSG 구조체를 사용한다.
이 구조체는 아래와 같다.
멤버 | 의미 |
hwnd | 메세지를 받을 윈도우 핸들 |
message | 어떤 종류의 메세지인가를 나타낸다. 가장 중요한 값 |
wParam | 전달된 메세지에 대한 부가적인 정보를 가진다. 어떤 의미를 가지는가는 메세지별로 다르다. 32비트 값이다. |
lParam | 전달된 메세지에 대한 부가적인 정보를 가진다. 어떤 의미를 가지는가는 메세지별로 다르다. 32비트 값이다. |
time | 메세지가 발생한 시간 |
pt | 메세지가 발생했을 때의 마우스 위치 |
message 멤버를 읽음으로써 메세지의 종류를 파악하며 message 값에 따라 프로그램의 반응이 달라진다.
wParam, lParam은 메세지에 대한 부가적인 정보를 가지되 메세지별로 의미가 다르다.
GetMessage 함수는 읽은 메세지를 MSG형의 구조체에 대입하며, 이 구조체는 DispatchMessage 함수에 의해 응용프로그램의 메세지 처리 함수(WndProc)로 전달된다.
메세지는 하나의 정수 값으로 표현되지만, 종류가 많아 암기할 수 없어 매크로 상수를 정의했다.
접두어는 WM_으로 시작된다.
메세지 | 의미 |
WM_QUIT | 프로그램을 끝낼 때 발생하는 메세지 |
WM_LBUTTONDOWN | 마우스의 좌측 버튼을 누를 경우 발생 |
WM_KEYDOWN | 키보드의 키를 누를 경우 발생 |
WM_CHAR | 키보드로부터 문자가 입력될 때 발생 |
WM_PAINT | 화면을 다시 그려야 할 필요가 있을 때 발생 |
WM_CREATE | 윈도우가 처음 만들어질 때 발생 |
WM_DESTROY | 윈도우가 메모리에서 파괴될 때 발생 |
메세지 루프가 종료되면 프로그램은 마지막으로 Message.wParam을 리턴하고 종료한다.
이 값은 WM_QUIT 메세지로부터 전달된 탈출 코드이며, 이 프로그램을 실행시킨 운영체제로 리턴된다.
윈도우 프로시저
WinMain 함수와 별도로 WnProc이라는 함수가 존재한다.
윈도우 프로시저 라는 뜻이지만 통상적으로 "윈드프록"으로 읽는다.
WndProc은 WinMain에서 호출하는 것이 아니라 운영체제에 의해 호출된다.
이렇게 운영체제에 의해 호출되는 응용 프로그램 내의 함수를 콜백(CallBack) 함수라고 한다.
WndProc의 인수는 4개이며, MSG 구조체의 앞쪽 멤버 4개와 동일하다.
1. hWnd는 메세지를 받을 윈도우의 핸들
2. iMessage는 어떤 종류의 메세지(어떤 변화가 발생했는가에 관한 정보를 가지고 있다.)
3. wParam, lParam은 iMessage의 메세지에 따른 부가적인 정보를 가진다.
(마우스 버튼이 눌렸다는 메세지가 입력되면, 화면의 어디서 마우스 버튼이 눌려졌는지 등의 부가정보)
메세지를 처리하는 WndProc은 아래와 같은 형태를 가진다.
switch(iMessage)
{
case Msg1:
처리1;
return 0;
case Msg2:
처리2;
return 0;
case Msg3:
처리3;
return 0;
default:
return DefWindowProc(...);
}
위와 같은 로직으로 실행되며, WndProc이 메세지를 무사히 처리했다면 0을 리턴하도록 약속되어 있다.
제일 끝에 있는 DefWindowProc 함수는 WndProc에서 처리하지 않은 나머지 메세지에 관한 처리를 한다. 이런 처리는 별도로 설정하지 않아도 함수에서 알아서 처리한다.
EX) 윈도우의 이동, 크기 변경 등의 처리
First 예제의 메세지 처리 함수는 아래와 같다.
LRESULT CALLBACK WndProc(HWND hWnd,UINT iMessage,WPARAM wParam,LPARAM IParam)
{
switch(iMessage){
case WM_DESTROY:
PostQuitMessage(0);
return 0;
}
return(DefWindowProc(hWnd,iMessage,wParam,lParam));
}
WM_DESTROY 라는 윈도우 파괴 메세지를 기다리는 것을 확인할 수 있으며, 나머지 메세지는 DefWindowProc에게 맡긴다.
WM_DESTROY(시스템 메뉴 더블클릭 - 끄기 / Alt + F4 와 같이 프로그램을 끝내려고 할 때 발생하는 메세지)
WndProc에서 이 메세지가 발생하면 PostQuitMessage 함수를 호출해서 WM_QUIT 메세지를 보낸다.
WM_QUIT 메세지가 입력되면 메세지 루프의 GetMessage 함수 리턴값이 FALSE가 되어 while 루프를 빠져나오며 WinMain이 종료된다.
즉, WN_DESTROY는 프로그램을 끝내는 중요한 처리를 하게 된다.
정리
아래는 메세지 루프까지의 접근을 그린 그림이며, WinMain 에서 시행되는 과정들이다.
아래는 메세지 루프에서 시행되는 과정들이다.
'개발 > Win API' 카테고리의 다른 글
3. 출력 (1) | 2025.01.02 |
---|---|
1. 윈도우 프로그래밍 (0) | 2024.11.25 |