F.R.I.D.A.Y.

Win32 프로그램 생성하기 3 본문

DEV/C C++

Win32 프로그램 생성하기 3

F.R.I.D.A.Y. 2021. 4. 2. 09:40
반응형

이제부터 코드를 사용합니다.


메인 함수 생성하기

Win32 프로그램은 Windows.h 헤더가 필요합니다. 그러니까 이전에 만들었던 main.cpp 첫 줄에 헤더를 추가합니다.

 

#include <Windows.h>

 그리고 나서 메인 함수를 생성합니다.

 자세히 보면, WinMain 함수의 세 번째 인짜 LPSTR은 변수 값이 존재하지 않습니다. 우리는 명령줄 인수를 받지 않을 것이므로 변수 이름은 작성하지 않았습니다.[# 그럼 지우면 안되냐구요? 안됩니다. 그럼 운영체제에서 프로그램을 실행시키는 WinMain을 못찾아요]

 

 여기까지 하면 일단 실행은 하는 프로그램이 만들어졌습니다. 그런데 우리는 이걸 원하지 않습니다. 창이 뙇! 하고 나오는 그런 프로그램을 원합니다. 아쉽게도 아직 그 자리까지 가기에는 멀었습니다.[# C++을 기반으로 아무 서드파티 라이브러리 없이 Windows 프로그래밍 하는 개발자들 보면 감탄을 해야하는 이유가 여기에 있습니다. 고작 윈도우 하나 생성하는데 수십 줄을 쓰고 해야됩니다. 이제라도 C#같은 .Net Framework 기반으로 옮겨도 늦지 않았습니다 여러분.]

 

 윈도우를 생성할 때는 WNDCLASS라는 구조체를 사용합니다. 이 구조체는 무엇이냐? 아래에 설명 나옵니다.

WNDCLASS

 윈도우는 OS에서 관리합니다. 모든 윈도우는 OS에서 관리하죠. 그렇기 때문에 윈도우를 생성하기 위해서는 OS에서 만들고자 하는 윈도우의 정보를 가지고 있어야합니다. 그런데 모든 프로그램이 같은 윈도우 특성을 가지고 있지는 않습니다. 그래서 Windows에서는 만들고자 하는 윈도우 정보를 등록할 수 있는 서류를 준비합니다. 그게 바로 WNDCLASS입니다.

 

 먼저 WNDCLASS 멤버부터 알아보겠습니다.

typedef struct tagWNDCLASSA {
  UINT      style;
  WNDPROC   lpfnWndProc;
  int       cbClsExtra;
  int       cbWndExtra;
  HINSTANCE hInstance;
  HICON     hIcon;
  HCURSOR   hCursor;
  HBRUSH    hbrBackground;
  LPCSTR    lpszMenuName;
  LPCSTR    lpszClassName;
} WNDCLASSA, *PWNDCLASSA, *NPWNDCLASSA, *LPWNDCLASSA;

 뭐가 뭔지 모르겠지요? 그렇습니다. 윈도우 하나 만드는데도 엄청난 노력이 필요합니다. 그러나 우리가 어떤 민족입니까, 모든 공부는 수능에 귀결되는 우리는 수능의 민족입니다. 모든 내용을 하나하나 외우겠습니다.

WNDCLASS 멤버

 구성 요소를 모두 알고 있다면 <WNDCLASS 채우기>로 넘어가세요.

UINT style

 생성할 윈도우의 스타일을 지정합니다. 윈도우 바깥의 테두리를 설정하기도 하고, 최대화 최소화 버튼을 설정하기도 합니다. 더 자세한 내용의 스타일 상수는 이 문서를 참고하세요.

 

WNDPROC lpfnWndProc

 윈도우 프로시저의 주소입니다. 이전에 윈도우 프로시저라고 하면서 WindowProc, 혹은 WndProc 함수를 생성했는데, 이 함수가 이 멤버로 연결됩니다. 즉, lpfnWndProc는 함수 포인터입니다.

 

 윈도우 프로시저는 우리 개발자가 만들지만, 윈도우 프로시저의 작업 위치는 우리가 정하지 않습니다. 1장에서 OS에서 입력 감지를 한다고 했던 설명 기억하나요? 맞습니다. 윈도우 프로시저는 이렇게 입력이 감지되어 OS로부터 호출된 프로시저만 작동[# 정확히는 프로그램이 시작된 순간부터 등록된 프로시저가 항상 실행됩니다. 다만, 이후에 설명할 메시지라는 녀석이 들어와야만 그에 맞게 연결된 작업을 하기 때문에 여기에서는 OS에서 순간순간 프로시저를 호출하도록 한다고 설명했습니다.]하도록 구성되어 있습니다.

 

대략적인 로직이라 생각하면 편합니다.

int cbClsExtra
int cbWndExtra

 윈도우 클래스 및 윈도우에 추가로 사용되는 여분 메모리입니다. 특수 목적으로 이용된다고 하는데, 어린 개발자 입장에서 전혀 사용하지 않을테니 값은 0으로 두면 되겠습니다.

 

HINSTANCE hInstance

 생성중인 현재 윈도우 클래스를 사용할 인스턴스 핸들을 입력합니다. 우리가 WinMain과 같은 엔트리 포인트를 생성할 때 첫 번째 인자로 hInstance를 작성했는데, 이 값이 이 멤버 변수에 들어갑니다.

HICON hIcon

 프로세스에 등록할 아이콘 이미지 핸들입니다. 프로그램을 실행시키면 타이틀바 왼쪽편에 조그맣게 아이콘이 있는데, 이 값을 설정함으로써 아이콘을 변경할 수 있습니다. NULL 값을 넘기면 기본 아이콘이 나타납니다.

 

HCURSOR hCursor

 프로그램 위에서 보여줄 커서의 이미지를 설정합니다. 물론, hIcon과 마찬가지로 커서 리소스의 핸들이 입력되어야 하며, NULL을 입력하면 기본 아이콘이 나타납니다.

 

HBRUSH hbrBackground

 윈도우의 배경 색상을 설정합니다. HBRUSH 값을 넘겨야하는데, 간단히 하려면 아래 상수를 HBRUSH로 강제 캐스팅해 넘기면 편합니다. 만일 이 항목 외에 더 다양한 색을 배경으로 사용하려면 실제 HBRUSH 객체를 생성해 넘겨주어야 합니다.

  • COLOR_ACTIVEBORDER
  • COLOR_ACTIVECAPTION
  • COLOR_APPWORKSPACE
  • COLOR_BACKGROUND
  • COLOR_BTNFACE
  • COLOR_BTNSHADOW
  • COLOR_BTNTEXT
  • COLOR_CAPTIONTEXT
  • COLOR_GRAYTEXT
  • COLOR_HIGHLIGHT
  • COLOR_HIGHLIGHTTEXT
  • COLOR_INACTIVEBORDER
  • COLOR_INACTIVECAPTION
  • COLOR_MENU
  • COLOR_MENUTEXT
  • COLOR_SCROLLBAR
  • COLOR_WINDOW
  • COLOR_WINDOWFRAME
  • COLOR_WINDOWTEXT
WNDCLASS wc;

wc.hbrBackground = (HBRUSH)COLOR_WINDOW; // 기본 윈도우 색상으로 설정됨
LPCSTR lpszMenuName

 윈도우의 메인 메뉴로 사용할 리소스의 이름입니다. 우리는 이거 사용 안합니다.

LPCSTR lpszClassName

 윈도우에는 다양한 윈도우 클래스(WNDCLASS)가 등록되어 있습니다. 우리가 개개인 별로 이름이 있듯이, 윈도우 클래스에도 이름이 존재합니다. Windows의 기본적인 컨트롤들(버튼, 리스트박스 등)과 같은 이름을 작성하면 해당 컨트롤처럼 이름이 존재하고, 만일 메모장과 동일한 클래스를 입력하면 해당 프로그램이 메모장처럼 변경됩니다. 따라서 윈도우 클래스 이름은 고유한 값을 사용하는 것이 좋습니다. 

 

 

WNDCLASSA (winuser.h) - Win32 apps

Contains the window class attributes that are registered by the RegisterClass function.

docs.microsoft.com

WNDCLASS 채우기

우리는 대부분의 값을 기본값으로 사용할 겁니다. 그럼에도 불구하고 꼭 필요한 세가지가 있습니다. lpfnWndProc, lpszClassName, hInstance 멤버입니다.

WNDCLASS wc{};

wc.lpfnWndProc = nullptr;
wc.lpszClassName = L"Hello world!";
wc.hInstance = hInstance;

 현재 lpfnWndProc는 nullptr로 설정되어 있습니다. 아직 윈도우 프로시저를 제대로 생성하지 않았기 때문입니다. 이 부분은 나중에 윈도우 프로시저 함수를 생성하면 그 때 변경할테니 내버려두기 바랍니다.

 

 wc{} 부분은 유니폼 초기화라 해서 C++ 11에서 새로 채택된 초기화입니다. 기존에 등호를 이용해 사용하는

WNDCLASS wc = {};

보다 간결해서 요즘에 제가 주로 사용하는 스타일입니다.

 

 이것으로 WNDCLASS는 모두 채웠습니다. 이제 OS에 등록하겠습니다. 여태까지는 OS에서 서류를 받아다가 빈 곳은 채워 넣었습니다. 그러니 등록을 하려면 채워넣은 서류를 다시 OS에 넘겨주어야겠죠.

 

 등록하는 함수는 RegisterClass 함수를 사용합니다.

RegisterClass(&wc);
더보기

# 중간 코드 점검

 여기까지 잘 따라왔다면 아래와 같은 코드가 완성되어 있습니다.

#include <Windows.h>

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR, int nCmdShow) {
	WNDCLASS wc{};

	wc.lpfnWndProc = nullptr;
	wc.lpszClassName = L"Hello world";
	wc.hInstance = hInstance;

	RegisterClass(&wc);

}

 앞으로 중간중간 포스트를 따라왔을 때 어떻게 코드가 작성되어 있는지를 살펴볼 수 있는 코드를 접은글에 넣어서 공개합니다. MS DOCS에서 제공하는 설명서에는 이렇게 중간중간 어떻게 코드가 작성되어 있는지 알 수 있는 정보가 없어서 공부하기 참 까다로웠습니다.

 

CreateWindow

 여태는 윈도우를 생성할 때 사용할 설계도면을 만들었습니다. 그럼 뭘 해야한다? 이제 만들 때입니다. 윈도우는 CreateWindow 함수를 이용해 만들 수 있습니다.

void CreateWindowW(lpClassName, lpWindowName, dwStyle, x, y,
   nWidth, nHeight, hWndParent, hMenu, hInstance, lpParam);

// 코드상 반환 타입이 void이지만, 실제로는 HWND입니다. 참고하세요.

 사실 CreateWindow는 매크로입니다. 즉, 해당 함수를 사용하면 다른 함수로 알아서 치환되는 것[# 그래서 위 코드에서는 CreateWindowW로 되어 있습니다.]입니다. 치환 기준은 유니코드를 사용하는 멀티바이트 프로그램이냐, ANSI를 사용하는 싱글바이트 프로그램이냐입니다. 사실 요즘들어서는 멀티바이트 프로그래밍을 해야합니다. 그래야 언어별로 표현이 가능할테니까요.

 

CreateWindow 인자

 11가지나 되는 인자가 있지만 우리는 크게 신경쓸 필요 없습니다. 만일 이미 알고 있는 내용이라면 <CreateWindow 채우기>로 넘어가세요.

lpClassName

 이전 WNDCLASS에 채우고 RegisterClass 함수로 운영체제에 등록했던 클래스 이름을 작성하면 됩니다. 누차 얘기하지만, 이 클래스 이름을 OS에서 기본으로 사용하는 클래스명[# Button이나 ComboBox와 같은 기본 선언된 클래스명, 참고 docs.microsoft.com/en-us/windows/win32/winmsg/about-window-classes]을 입력하면 컨트롤이 나오니까 이점은 꼭 유의합시다.

 

lpWindowName

 윈도우 타이틀에 들어갈 텍스트가 이 인자로 결정됩니다.

 

dwStyle

 윈도우 스타일입니다. 타이틀바를 표시할 것인지, 테두리 표시 표시 여부, 최대/최소화 버튼 표시 등을 여기에서 관리합니다.

 

x, y

 생성한 윈도우를 디스플레이의 어느 위치에 놓을 것인지를 선택합니다.

 

nWidth, nHeight

 생성할 윈도우의 크기를 결정합니다. nWidth는 너비, nHeight는 높이를 설정합니다.

 

hWndParent

 부모 윈도우가 있다면 부모 윈도우의 핸들을 입력합니다. 대개 NULL(nullptr)을 넣으면 됩니다. 컨트롤인 경우에는 컨트롤이 들어갈 윈도우의 핸들을 넣으면 됩니다.

 

hMenu

 윈도우의 주 메뉴로 사용할 메뉴바의 핸들값을 넘기면 됩니다. 우리는 NULL로 유지할 겁니다.

 

hInstance

 윈도우를 가지고 있는 인스턴스(프로세스) 핸들을 넘겨줍니다. WinMain의 첫 번째 인자를 넘겨주면 됩니다.

 

lpParam

 추가 메모리입니다. NULL로 유지합니다.

 

 

CreateWindow 채우기
HWND hwnd = CreateWindow(L"Hello world", L"My First Win32 Program", WS_OVERLAPPEDWINDOW, 
		CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 
		nullptr, nullptr, hInstance, nullptr);

 윈도우가 나타날 위치와 크기는 기본값으로 설정했습니다.

 

 


이야기가 길어지는 관계로 다음 포스트에서 이어합니다.

# index

728x90
반응형

'DEV > C C++' 카테고리의 다른 글

Win32 프로그램 생성하기 5  (0) 2021.04.02
Win32 프로그램 생성하기 4  (2) 2021.04.02
Win32 프로그램 생성하기 2  (0) 2021.04.01
Win32 프로그램 생성하기 1  (0) 2021.03.31
Visual Studio 2019에서 프로젝트 만들기  (0) 2020.09.07
Comments