API 기본구조 살펴보기 1 , 복습
Windows 데스크톱 애플리케이션으로 프로젝트를 생성하면 기본적으로
제공되어 생성되어있는 코드들이 있다.
F5로 실행해보면 윈도우창 하나가 뜬다.
기본적으로 마이크로소프에서 윈도우에서 C++ 프로그램을 만들기위한
윈도우창 하나를 만들어준거라고 볼 수 있다.
그러면 마이크로소프에서 프로그래머를 위해 어떤거를 제공했는지를 파악해보자.
기본적으로 디버깅(F10 실행)을 해보면서 시작부터 차근차근 하나씩 알아볼 수 있다.
#define MAX_LOADSTRING 100
// 전역 변수:
HINSTANCE hInst; // 현재 인스턴스입니다.
WCHAR szTitle[MAX_LOADSTRING]; // 제목 표시줄 텍스트입니다.
WCHAR szWindowClass[MAX_LOADSTRING]; // 기본 창 클래스 이름입니다.
// 이 코드 모듈에 포함된 함수의 선언을 전달합니다:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 여기에 코드를 입력합니다.
// 전역 문자열을 초기화합니다.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_MY210107, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 애플리케이션 초기화를 수행합니다:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY210107));
MSG msg;
// 기본 메시지 루프입니다:
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// 함수: MyRegisterClass()
//
// 용도: 창 클래스를 등록합니다.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY210107));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MY210107);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// 함수: InitInstance(HINSTANCE, int)
//
// 용도: 인스턴스 핸들을 저장하고 주 창을 만듭니다.
//
// 주석:
//
// 이 함수를 통해 인스턴스 핸들을 전역 변수에 저장하고
// 주 프로그램 창을 만든 다음 표시합니다.
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 인스턴스 핸들을 전역 변수에 저장합니다.
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 함수: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 용도: 주 창의 메시지를 처리합니다.
//
// WM_COMMAND - 애플리케이션 메뉴를 처리합니다.
// WM_PAINT - 주 창을 그립니다.
// WM_DESTROY - 종료 메시지를 게시하고 반환합니다.
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// 메뉴 선택을 구문 분석합니다:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 여기에 hdc를 사용하는 그리기 코드를 추가합니다...
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// 정보 대화 상자의 메시지 처리기입니다.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
시작부터 하나씩 보자면 여러가지 함수들을 호출해주고 있고 #define을 사용하여 치환해서 사용하고 있다.
제일 상단에 전역변수들을 살펴보면 WCHAR를 타고들어가보면
typedef wchar_t WCHAR;
이렇게 되어있다.typedef는 자료형을 자기 마음대로 별명을 지어줘서 사용하는 문법이다.
결국에는 wchar_t szTitle[100] 이 코드와 같은것이다.
아래로 내려가면
ATOM My MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
이런식으로 되어있는데 이거는 단순히 함수의 선언과 정의를 분리해 놓은거다.
그러면 여기서 BOOL을 타고 들어가보자.
생각으로는 단순히 bool을 재정의 해놨나 싶은데 타고 들어가보면 int를 재정의 해놓은것이다
주의)어느정도 감이 잡혔다고 아 이거는 이런거겠구나 단순히 넘겨짚지 말자!
그러면 왜 int를 BOOL로 재정의 해놓고 쓰는걸까?bool은 원래 C문법에 존재하지 않았던 자료형이기 때문이고 윈도우는 대부분 C로 짜여져있다.C++이 C의 문법을 많이 포함했을뿐이고 그때의 잔재라고 볼 수 있다.
다음으로 보면 Main 함수의 형태도 좀 바뀌었다.wWinMain 앞에 있는 APIENTRY를 타고 들어가보면
WINAPI를 치환해서 사용하고 있고 그걸 또 타고들어가보면
결국엔 __stdcall 이라는걸 치환해서 사용하고 있는것이다.
복습)
__stdcall 이란 함수 호출 규약으로 알고있다.함수 호출 규약이란 함수를 호출 할 때 명시하는 약속으로
대표적으로 __thiscall,__stdcall,__cdecl이 있다.이는 함수를 실행해주는 호출자와 함수의 사용이 끝나면
함수를 정리해주는 정리자에따라 구분되어있고 __stdcall과__cdecl은 전역함수들이 사용하며
__thiscall 은 클래스 내부에서 this를 사용하는 멤버함수들이 사용한다.
이제 Main의 형태를 봐보자.Main의 이름이 바뀌었는데 이는
프로젝트->속성->링커->시스템으로 가보면
이렇게 나와있고 API는 창으로 되어있다.만약 콘솔로 바꾸면은 알고있던 main()를 찾을 수 없다고 실행이 안된다.
기본 설정인 창으로 하면 wWinMain 이 함수를 진입점으로 하겠다는 것이된다.
다음으로 들어오는 인자값들을 살펴보자.Main Parameter 글을 참조해서 보면
진입점 함수가 실행될때 기본으로 들어오는 인자값이 있고 API에서는 저런 인자값들을
기본으로 윈도우가 넣어준다.첫 인자인 _In_ HINSTANCE hInstance 살펴보면
HINSTANCE 타고 들어가면 DECLARE_HANDLE(HINSTANCE); 이렇게 나와있고
DECLARE_HANDLE타고 들어가서 보면 struct name##__{int unused;}; typedef struct name##__ *name
이렇게 정의되어있다.이거는 비어있는 struct를 생성하는 문법이고 안에는 int 하나를 가지고 있다.
struct HINSTANCE__
{
int unused;
};
이렇게 볼 수 있다.사실 그냥 int라고 볼 수 있다.
윈도우(OS)의 기능들을 이용하려면 (윈도우 창,윈도우 전용함수 등) 윈도우(OS)의 허락을 받아야 한다.
기본 API을 생성하고 실행하면 나오는 창의 기본크기를 바꾸고 싶다면 이 창을 만든 윈도우(OS)에게
요청해야 한다.
WindowSize(100, 200);
이를 만약 이런식으로 요청한다고 가정하면 어떤 창의 크기가 바꾸라는 거인지 알 수 없다.
그래서 윈도우가 HINSTANCE hInstance 이 값을 주는 것이다.만약에 저기서 1000이라는 값을 받았으면
WindowSize(1000, 100, 200);
이런식으로 요청한다고 볼 수 있다.그러면 이제 내가 원하는 창의 크기가 100,200으로 바뀌는 개념이다.
이를 핸들(int 하나 들어있는 구조체)이라고 한다.
_In_ HINSTANCE hInstance 얘는 프로그램 그 자체의 핸들이다.
// 전역 문자열을 초기화합니다.
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_MY210107, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// 애플리케이션 초기화를 수행합니다:
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
여기서는 윈도우가 우리 프로그램에 기본적인 문자열을 몇개 제공해준다.
프로젝트에 리소스 파일 폴더를 보면 프로젝트명.rc 라는 파일이 있고 여기에 들어가보면
String Table 이라는 폴더에 파일이 있다.거기에 보면 103이라는 값에 프로젝트명이 들어가있다.
LoadStringW의 인자값에 IDS_APP_TITLE을 보면 103이 치환되어 있고 이 문자열을 입력해주는 함수이다.
다음으로 MyRegisterClass를 살펴보면
OS한테 창을 띄워달라고 부탁하는 방법은 2가지 과정을 거친다.
1.OS에게 나는 어떤 윈도우(창)을 만들고 싶은지 알려준다.그리고 그 윈도우(창) 형식에 이름을 붙인다.
(예를들어 내가 만약 띄우고싶은 창의 형식에 이름을 Test라고 지었다면)
2.Test형식의 MyWindow라는 이름을 붙여서 띄워줘 요청한다.
여기서 내가 만드려는 윈도우의 형식인 Test와 내가 만드려는 윈도우인 MyWindow는
쉽게 생각해서 클래스와 객체로 생각하면 된다.클래스는 사용자 정의 자료형이고
기본 자료형이 int,char 이런것들도 그 자체로는 사용이 불가능하다.
이 개념이 내가 만드려는 윈도우의 형식이 되는거다.
예를 들어 클래스 A를 만들고 A NewA; 이런식으로 객체를 만들어서 사용할것이다.
여기서 NewA 즉 객체의 개념이 MyWindow가 되는것이다.
만약 위의 방식이 아니라면 우리는 그림판 프로그램을 하나 키고 그림판을 하나 더 키는일이
불가능하다는 말과 같다.그렇기 때문에 각각의 핸들을 받아서 원하는 프로그램을
핸들을 이용해서 컨트롤 할 수 있는거다.
MyRegisterClass(hInstance);는 어떤 윈도우를 띄울지 등록시켜주는 역할을 한다.
다음으로
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
여기서는 윈도우(창)를 띄워주는 코드이다.InitInstance함수를 타고 들어가보면
CreateWindowW 함수가 있고 여러가지 인자값들이 있다. 이 함수의 원형을 보면
HWND CreateWindow(
lpClassName,
lpWindowName,
dwStyle,
x,
y,
nWidth,
nHeight,
hWndParent,
hMenu,
hInstance,
lpParam);
이렇게 되어있는데 하나씩 살펴보면
lpClassName : 윈도우(창) 형식의 이름.즉 MyRegisterClass함수를 사용해 등록한 이름을 말한다.
lpWindowName : 내가 띄우고 싶은 윈도우(창)의 이름.
dwStyle: 윈도우(창)를 생성할 때 지정하는 스타일.여러가지의 스타일을 지정해서 사용할 수 있다.
x,y : 윈도우(창)을 생성할 x,y 좌표값.
nWidth,nHeight : 윈도우(창)의 너비,높이 값.
hWndParent : 생성될 이 윈도우(창)를 가질 부모의 핸들값 없을 경우(최상위 윈도우)
hMenu : 사용할 메뉴의 핸들.
hInstance : 윈도우(OS) 클래스로부터 식별할 수 있는 식별자 값
lpParam : 추가 윈도우(사용자 정의 데이터) 매개변수
CreateWindowW함수가 실행되고 ShowWindow 함수가 실행된다.이 ShowWindow 함수가
실행되면서 윈도우(창)이 띄워진다.여기서 인자값을 보면 CreateWindowW 함수로 리턴된
값을 사용한다.
ATOM MyRegisterClass(HINSTANCE hInstance) 에 보면 이 함수는
우리가 만들 윈도우의 형식을 설정하는 함수이다.
즉 어떤 형태의 윈도우를 만들지 설정한다.
WNDCLASSEXW wcex; 이렇게 해서 wcex를 생성해서 사용하고있는 코드가 있다
그럼 WNDCLASSEXW를 타고 들어가보면 typedef struct로 정의되어있다.
복습)
C에서 사용하던 문법으로 C에서 struct를 만들면
struct A
{
};
int main()
{
struct A NewA;
}
이런식으로 사용해야 한다.
typedef struct _A
{
} A;
int main()
{
A NewA;
}
typedef를 이용해서 이렇게 사용하기 위한 문법이다.
아래의 값들을 우리가 만드려는 윈도우의 아이콘,메뉴이름,커서 등등을 설정하는 값들이다.
OS는 우리가 원하는 윈도우의 설정을 모르기 때문에 여기서 알려줘야 한다.
위에서부터 살펴보면
wcex.lpfnWndProc = WndProc;
이런 코드가 있는데 WndProc로 타고 들어가보면 콜백함수로 되어있고 여기서는
윈도우(창)에 어떤일이 발생했을때 우리가 어떻게 처리할건지 윈도우(OS)는 모르니
우리가 만든 함수를 알려주면 윈도우(창)에 무슨일이 발생했을때 마다 그에맞는
함수를 실행시켜주는 역활을 한다.