Win32 API 기초 : Win32 API 기본 (1)
Win32 API 프로젝트를 위해
비주얼 스튜디오를 켜서 "새 프로젝트 만들기"를 클릭합니다.
그러면 아래와 같은 창이 뜨는데
"Windows 데스크톱 애플리케이션"을 선택하고 다음을 누릅니다.
프로젝트 이름과 솔루션 이름, 위치를 설정해 주고 만들기를 누릅니다.
우측에 '솔루선 탐색기'에서
소스 파일에 생성된 파일을 클릭하면,
아래와 같은 코드가 보일 겁니다.
사용자가 직접 코드를 작성한 적이 없음에도 프로젝트 생성만으로
많은 코드가 미리 작성되어 있는 모습이네요.
먼저 11, 12번째 줄에 있는 WCHAR가 무엇인지 봅시다.
WCHAR를 클릭하고 F12를 눌러 보세요.
그럼 위 사진과 같은 코드로 이동할 겁니다.
WCHAR가 의미하는 것은 wchar_t 자료형을 재정의하여 나타내는 것이었네요.
즉 WCHAR는 wchar_t와 완전히 같은 것이라고 생각하면 되겠습니다.
또한 배열의 크기를 MAX_LOADSTRING으로 하였는데
7번째 줄에서 볼 수 있듯이 상수 100을 의미하죠?
wchar_t형 szTitle과 szWindowClass는 100칸짜리 배열인 것입니다.
15 ~ 18번째 줄에 있는 함수들은 20번째 줄의 main 함수가 실행되며
함수를 사용할 때 오류가 발생하지 않도록 전방 선언된 것입니다.
해당 함수들의 정의가 main 함수 아래쪽에 작성되어 있기 때문이죠.
main 함수는 4개의 인자를 받습니다.
첫 번째 인자 HINSTANCE형 hInstance는 실행된 프로세스의 시작 주소를 의미합니다.
두 번째 인자 HINSTANCE형 hPrevInstance는 이전에 실행된 프로세스의 시작 주소를 의미합니다.
세 번째 인자 LPWSTR형 lpCmdLine은
위 그림처럼 명령 프롬프트에서 명령어를 실행할 때
옵션을 줄 수 있는 것( -e 부분 )과 같은 역할을 해 줍니다.
lpCmdLine 인자가 어떤 값이냐에 따라 다른 동작을 하게 되겠죠?
LPWSTR를 클릭하여 F12를 눌러 보면
위와 같은 코드로 이동합니다. LPWSTR가 의미하는 것은
WCHAR*, 즉 wchar_t*인 것이죠.
근데 각 인자의 자료형 앞에 _In_ 또는 _In_opt_가 붙은 것이 보입니다.
이는 SAL이라고 해서, 소스 코드 주석 언어입니다.
해당 인자가 어떤 용도인지 알려주는 주석 같은 것이라고 보면 됩니다.
_In_이 붙으면 해당 인자(데이터)가 함수에 입력된다는 의미,
_In_opt가 붙으면 Optional, 즉 부가적인 인자라는 것이죠.
25 ~ 26번째 줄을 보시면
UNREFERENCED_PARAMETER라는 구문이 보입니다.
F12를 눌러 내용을 살펴보면
UNREFERENCED_PARAMETER 괄호 옆에 들어간 것을
그래도 괄호 옆에 들어간 것으로 치환하도록 정의되어 있습니다.
즉 UNREFERENCED_PARAMETER(hPrevInstance);와
UNREFERENCED_PARAMETER(lpCmdLine);는 사실
hPrevInstance;와 lpCmdLine; 같은 겁니다.
아무 의미 없는 코드인 것이죠.
UNREFERENCED_PARAMETER라는 이름에서
알 수 있듯이 단순히 해당 인자들은 main 함수에서
딱히 사용되지 않음을 의미하기 위해 넣은 코드인 겁니다.
주석과 같다고 생각하시면 됩니다.
hPrevInstance는 예전에는 필요했을 겁니다.
하지만 윈도우 버전이 업데이트되면서 필요가 없어진 것이겠죠.
그림판을 하나 켠다고 가정해 봅시다. 이 처음 켠 그림판이 할당된
주솟값이 있을 겁니다. 이후 그림판을 한 개 더 열었습니다.
그럼 두 그림판의 주솟값을 다르겠죠? 그래서 이전에는
이전의 켠 그림판의 주솟값을 받고 다른 주솟값에 그림판을 할당했습니다.
이 과정에서 이전 주솟값을 인자로 받기 위해 hPrevInstance가 필요했겠죠.
하지만 이제는 그림판을 여러 개 켜도 모든 그림판의 주솟값은 같습니다.
실제로는 다르겠지만 사용자 입장에서 봤을 때 같습니다.
이것이 가능한 이유가 가상 메모리 덕분입니다.
아무튼 이러한 이유로 hPrevInstance는 더 이상 필요가 없어진 것일테죠.
31 ~ 32번째 줄을 보시면 LoadStringW 함수가 보입니다.
주석을 통 전역 문자열을 초기화해 주는 구문이라고 알려주네요.
10번째 줄에 있는 전역 변수 hInst의 값을 main 함수가 실행될 때
인자로 받은 hInstance의 값으로 초기화하고,
11번째 줄에 있는 전역 변수 szTitle의 값을 IDS_APP_TITLE을 통해
초기화해 주고,
12번째 줄에 있는 전역 변수 szWindowClass의 값을 IDC_CLIENT을 통해
초기화해 주는 겁니다.
이때 IDS_APP_TITLE과 IDC_CLIENT을 통해
초기화해 주는 것의 의미를 알기 위해서 'Ctrl + Shift + E'를 입력하여
"리소스 뷰"를 확인합시다.
리소스 뷰는 우리가 프로젝트에서 사용할
리소스를 확인할 수 있는 창입니다.
여기서 String Table 파일을 열어 주세요.
그럼 아래와 같은
문자열 테이블이 열립니다.
IDS_APP_TITLE이라는 ID를 가진 문자열은
값이 103이며, 캡션에는 프로젝트를 생성할 때
작성한 프로젝트 이름으로 되어 있을 겁니다.
IDC_CLIENT이라는 ID를 가진 문자열은
값이 109이며, 캡션에는 프로젝트를 생성할 때
작성한 프로젝트 이름이 대문자로 되어 있죠.
다시 솔루선 탐색기로 돌아가서
헤더 파일인 Resource.h 파일을 열어 보면
IDS_APP_TITLE과 IDC_CLIENT이 각각 상수 103, 109로 선언되어 있습니다.
다시 main 함수로 돌아와서
31 ~ 32번째 줄을 다시 보면 szTitle은 103에 해당하는 Client로,
szWindowClass는 109에 해당하 CLIENT로 초기화될 것임을 알 수 있습니다.
이것을 해 주는 이유는 사용자가 프로젝트를 만든 후에 따로 지정하지 않으면
윈도우 창의 이름을 프로젝트 이름으로 하겠다라는 의미입니다.
그래서 실행을 해 보면
윈도우 창에 표시되는 이름이 프로젝트 이름으로 출력되고 있습니다.
윈도우 창에 표시되는 이름을 나타내는 szTitle을 Client라는 문자열로
초기화해 주었기 때문이죠.
main 함수에서 보면
InitInstance 함수를 통해 애플레케이션 초기화를
수행한다고 되어 있습니다. InitInstance 함수의 정의를 살펴보면,
100번째 줄에서
CreateWindowW를 통해 창을 만들어 주는데 이때 szTitle이
들어가는 것이 보입니다.
사실 String Table을 통해 szTitle을 초기화한 후
이 초기화한 szTitle을 인자로 주어 제목을 설정하지 않고
코드상에서 직접 szTitle 대신 문자열을 넣어도 됩니다.
문자열을 직접 코드상에 입력하여 실행하였더니
해당 문자열이 제목으로 설정이 되었습니다.
다시 main 함수를 봅시다.
33번째 줄에서 MyRegisterClass 함수가 보입니다.
해당 함수의 정의를 살펴보시죠.
MyRegisterClass 함수는 창 클래스를 등록하는 역할을 한다고 하네요.
사용자가 열려고 하는 윈도우 창의 정보를 등록하는 것이라고 보면 되겠습니다.
WNDCLASSEXW형 객체 wcex를 만들어
멤버를 초기화하고 있습니다. 여기서 주목해 볼 멤버는
79번째 줄에 있는 lpszMenuName와 80번째 줄에 있는 lpszClassName입니다.
lpszMenuName는 프로그램을 실행하여 창이 열렸을 때
나오는 메뉴 표시줄입니다.
"파일", "도움말" 같은 메뉴를 의미하는 것이죠.
그래서 lpszMenuName에 nullptr을 대입하면
메뉴가 나타나지 않습니다.
lpszClassName은 키값으로,
등록하고자 하는 창의 이름이죠.
szWindowClass를 대입하도록 되어 있네요.
szWindowClass는 자동으로 프로젝트의
이름을 대문자로 하여 초기화가 진행되었고
이를 lpszClassName에 대입하는 겁니다.
정리해 보면,
MyRegisterClass 함수를 통해 창의 정보를 등록한 후
36번째 줄에 있는 InitInstance 함수를 호출합니다.
그러면 MyRegisterClass 함수를 통해 등록한 창의 정보를 가지고
InitInstance 함수 속 CreateWindowW의 인자로 하여 창을 만드는 것입니다.
이때 등록한 창을 찾기 위해
키값인 lpszClassName을 이용하는 것이죠.
CreateWindowW의 첫 번째 인자와 lpszClassName의 값이
같아야 프로그램이 정상적으로 실행이 됩니다.
두 값이 같다면 꼭 전역 변수인 szWindowClass로 초기화하거나
인자로 주지 않고 직접 입력해 줘도 프로그램은 문제 없이 작동합니다.
InitInstance 함수에서 hWnd가 제대로 초기화되지 않으면
103번째 줄로 인해 FALSE를 반환합니다.
그러면 main 함수 36번째 줄에 있는 if 문 안의 코드가 실행되면서
main 함수가 FALSE를 반환하고 종료되어 프로그램이 종료됩니다.
주의할 점은 윈도우 창이 곧 프로세스가 아니라는 점입니다.
윈도우 창이 없다고 해서 프로세스가 실행되지 않은 게 아니며,
윈도우 창을 껐다고 해서 프로세스가 꺼지는 것이 아닙니다.
우리가 컴퓨터를 켜서 그림판과 인터넷 창을 켰다고 했을 때,
두 개의 프로세스만이 실행 중인 것이 아니라는 것이죠.
작업 관리자를 보시면 수많은 프로세스가 실행 중인 것을
확인할 수 있습니다.
다만 위 main 함수에서는 46번째 while 문을 통해
main 함수가 종료되지 않는 상태에 있다가
윈도우 창이 꺼지는 순간 while 문을 빠져나오면서
프로그램이 종료되게 한 것입니다.
강의 출처 : https://www.youtube.com/watch?v=dlFr-OnHlWU&list=PL4SIC1d_ab-ZLg4TvAO5R4nqlJTyJXsPK