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

Win32 BaseWindow Class 생성하기 본문

DEV/C C++

Win32 BaseWindow Class 생성하기

F.R.I.D.A.Y. 2021. 4. 5. 14:04
반응형

 지난 시간에 우리는 WinMain 함수와 WindowProc 함수를 생성하고 Windows Hello의 사진 암호와 비슷하게 작동하는 프로그램을 만들어 봤습니다.

 

 프로그램은 비단 하나의 윈도우만을 가지고 있지는 않습니다. 따라서 윈도우에서 여러 번 사용할 수 있는 코드를 만들어 볼겁니다.

 

※ C++의 기본 지식이 필요합니다.

 


들어가며

 이 포스트는 MS DOCS의 Get Started with Win32 and C++에 기반을 두고 있습니다.

 

Get Started with Win32 and C++ - Win32 apps

The aim of this Get Started series is to teach you how to write a desktop program in C++ using Win32 and COM APIs.

docs.microsoft.com

 

 

BaseWindow 생성

 BaseWindow 클래스는 윈도우를 생성할 때, 공통적인 작업을 모아놓은 클래스입니다. 따라서 아래의 요소가 필요합니다.

  • 윈도우 프로시저
  • 메시지 핸들러
  • 윈도우 생성

 

헤더 생성

 먼저 BaseWindow의 헤더 파일을 생성합니다.

 그 다음 아래 코드를 작성합니다.

#include <Windows.h>

template<class DERIVED_TYPE>
class BaseWindow {

};

 템플릿 문법을 적용한 것은 다음에 나올 윈도우 상태 정보를 저장하고, 관리 및 접근이 용이하기 위함입니다.

 

 먼저 윈도우 프로시저와 윈도우를 생성하는 함수를 작성하겠습니다. 이 함수는 외부에서 접근을 해야하는 함수이므로, 접근제한자는 public으로 정하겠습니다.

public:
	static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {

		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

	bool Create(LPCWSTR lpWindowName, DWORD dwStyle, DWORD dwExStyle = 0,
		int x = CW_USEDEFAULT, int y = CW_USEDEFAULT,
		int nWidth = CW_USEDEFAULT, int nHeight = CW_USEDEFAULT,
		HWND hWndParent = nullptr, HMENU hMenu = nullptr
	) {

	}
더보기

# 윈도우 프로시저 함수 앞의 static은 무엇인가요?

 윈도우 프로시저 함수로 멤버 함수를 사용할 때는 static으로 선언된 멤버 함수만 이용이 가능합니다. 그래서 static 키워드가 붙은 것입니다.

 멤버 함수는 객체의 private 변수에 접근할 수 있습니다. 이 과정에서 this라는 포인터가 들어가게 되고, 이는 결국 함수를 사용할 때 그 객체의 주소도 알고 있어야한다는 소리가 됩니다. 그러나 함수 포인터에서는 함수의 주소 하나만 넘기게 되니 함수 포인터에서는 객체의 멤버 함수는 접근하기가 사실상 불가능하다고 생각하면 될 것 같습니다. 물론 가능은 하지만, 제약이 많다고 말할 수 있겠습니다.

 

 

Create(...)

 굉장히 많은 정보가 인자로 들어가 있습니다. 첫 인자는 윈도우 타이틀에 보일 이름, 두 번째 인자는 윈도우의 스타일을 지정하는 인자입니다. 결국 CreateWindow 함수를 이용하는데 꼭 필요한 것들이라는 소리가 되죠.

 

 기존의 윈도우를 생성하는 코드처럼 작성해줍니다. 단, 이번에는 윈도우의 상태 정보를 저장할 수 있는 부가 메모리를 넣을 예정이므로 주의해서 읽고 따라 작성합시다.

 

		WNDCLASS wc{};

		wc.lpfnWndProc = DERIVED_TYPE::WindowProc;
		wc.hInstance = GetModuleHandle(nullptr);
		wc.lpszClassName = ClassName();
		wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
		wc.hbrBackground = (HBRUSH)COLOR_WINDOW;

		RegisterClass(&wc);

 코드를 작성하다 보면, 윈도우 클래스명과 CreateWindow 함수의 결과로 반환될 HWND 값을 저장할 변수가 필요합니다. 이 값은 클래스 내부에서 매번 사용하므로 클래스 멤버로 추가하고 그 값을 가져오도록 합니다.

 

 

 윈도우 핸들을 저장하고 있는 hWnd 변수의 경우 외부에서 핸들 값을 넘겨줄 때 필요하나 그 값의 변경이 이루어지면 안되므로 get 함수를 생성해 접근할 수 있도록 합니다. 그리고 ClassName의 경우에는 지금 작성중인 BaseWindow 클래스로 윈도우를 만들 것이 아니라, 이 BaseWindow 클래스를 상속한 새로운 클래스에서 사용할 것이므로 가상함수로 선언해 상속시에 필수로 작성하도록 합니다.

 이런 이유로 접근 제한자 또한 public이 아니라 protected로 설정합니다.

protected:
	HWND hWnd;
	virtual LPCWSTR ClassName() const = 0;

 

 이어서 윈도우를 생성합니다.

		hWnd = CreateWindow(ClassName(), lpWindowName, dwStyle, x, y,
			nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(nullptr), this);

		return hWnd ? true : false;
더보기

# 마지막 this 포인터는 무엇인가요?

 이전까지는 마지막 값으로 NULL을 넣어주었습니다. 이 값은 윈도우가 생성되고 나서 WM_NCREATE에서 전달되는 값들 중 하나입니다. 위에서 윈도우 상태를 포함할 수 있는 부가 메모리를 추가할 것이라 했습니다. 부가 메모리가 이 값이 될 것입니다.

더보기

# Create(...) 함수 코드

	bool Create(LPCWSTR lpWindowName, DWORD dwStyle, DWORD dwExStyle = 0,
		int x = CW_USEDEFAULT, int y = CW_USEDEFAULT,
		int nWidth = CW_USEDEFAULT, int nHeight = CW_USEDEFAULT,
		HWND hWndParent = nullptr, HMENU hMenu = nullptr
	) {
		WNDCLASS wc{};

		wc.lpfnWndProc = DERIVED_TYPE::WindowProc;
		wc.hInstance = GetModuleHandle(nullptr);
		wc.lpszClassName = ClassName();
		wc.hCursor = LoadCursor(nullptr, IDC_ARROW);
		wc.hbrBackground = (HBRUSH)COLOR_WINDOW;

		RegisterClass(&wc);

		hWnd = CreateWindow(ClassName(), lpWindowName, dwStyle, x, y,
			nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(nullptr), this);

		return hWnd ? true : false;
	}

 

WindowProc(...)

 두 부분으로 나누어서 설명할 겁니다. 이 부분은 꼭 필요합니다. 나중에 메시지 핸들링을 위한 초석이 되거든요.

 

윈도우 상태 정보 저장

 먼저 윈도우 상태를 가리킬 수 있는 포인터를 하나 생성합니다. 윈도우 상태 정보는 BaseWindow이므로, 이를 상속받는 클래스 또한 연결지을 수 있습니다.

		DERIVED_TYPE* pThis = nullptr;

		if (uMsg == WM_NCCREATE) {
			CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
			pThis = reinterpret_cast<DERIVED_TYPE*>(pCreate->lpCreateParams);
			SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
			pThis->hWnd = hWnd;
		}
		else {
			pThis = reinterpret_cast<DERIVED_TYPE*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
		}

 이 작업은 바로 이어서 나올 메시지 핸들러를 연결짓는데 사용되기 때문에 꼭 필요합니다. 그리고 pThis의 멤버 변수인 hWnd에 인자 hWnd 값을 꼭 넘겨주어야 합니다.

 

 BaseWindow 클래스는 실제로 이벤트 핸들링[# OS에서 보내오는 메시지에 따라 다양한 작업을 하도록 만드는 작업이라 생각하면 됩니다.]을 하지 않습니다. 윈도우 프로시저에서 저장한 DERIVED_TYPE이 가진 멤버 함수를 이벤트 핸들러로 이용할 겁니다.[# 잘 모르겠다면 윈도우 프로시저 함수는 실제 이벤트 핸들러 함수를 호출하기 위한 함수라고 생각하면 됩니다.]

 

이벤트 핸들러 생성

 실제 메시지 처리는 이벤트 핸들러 메서드에서 처리할 것입니다. 그러나 BaseWindow로 객체를 만들것이 아니므로, 가상 함수로 선언하겠습니다. 이벤트 핸들러는 클래스 외부에서는 호출하지 않기 때문에 접근 제한자는 protected로 하겠습니다.

protected:
	HWND hWnd;
	virtual LPCWSTR ClassName() const = 0;
	virtual LRESULT HandleMessage(UINT, WPARAM, LPARAM) = 0;
	// 기존에 이미 protected로 선언된 곳에 추가되었음.

 이 함수가 호출되었다는 것은 이 함수가 정의된 곳에서 불려졌다는 소리가 됩니다. 즉, 이미 멤버 함수로 hWnd가 존재하기 때문에 인자로 윈도우 핸들을 받을 필요가 없습니다. 이런 이유로 인자는 총 세개로 줄었습니다.

 

 이제 다시 WindowProc로 돌아와서 이어서 작성합니다. pThis가 이벤트 핸들러를 가지고 있는 객체를 가리키게 되므로, if 구문으로 이벤트 핸들러를 호출하도록 합니다.

		if (pThis) {
			return pThis->HandleMessage(uMsg, wParam, lParam);
		}

		return DefWindowProc(hWnd, uMsg, wParam, lParam);

 만일 pThis가 존재하지 않다면 DefWindowProc를 호출하도록 합니다.

더보기

# WindowProc의 완성 코드

	static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
		DERIVED_TYPE* pThis = nullptr;

		if (uMsg == WM_NCCREATE) {
			CREATESTRUCT* pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
			pThis = reinterpret_cast<DERIVED_TYPE*>(pCreate->lpCreateParams);
			SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
			this->hWnd = hWnd;
		}
		else {
			pThis = reinterpret_cast<DERIVED_TYPE*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
		}

		if (pThis) {
			return pThis->HandleMessage(uMsg, wParam, lParam);
		}

		return DefWindowProc(hWnd, uMsg, wParam, lParam);
	}

 

 

외부에서 핸들 접근하기

 객체가 저장중인 윈도우 핸들은 외부에서 접근할 수가 없습니다. 따라서 get 함수를 생성해주겠습니다.

	HWND Window() const {
		return hWnd;
	}

 이것으로 BaseWindow의 작성이 완료되었습니다.

 


Next.

 

Win32 MainWindow Class 생성하기

 이전 포스트에서 작성한 BaseWindow를 기반으로 한 MainWindow를 구성하겠습니다. MainWindow  먼저 파일을 분리하겠습니다. mainWin.h 파일을 생성합니다.  그리고 BaseWindow를 상속하기 위해 baseWin.h를..

pang2h.tistory.com

 

# index

728x90
반응형
Comments