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++의 기본 지식이 필요합니다.

 


Index

  • 들어가며
  • BaseWindow 생성
    • 헤더 생성
    • Create(...)
    • WindowProc(...)
      • 윈도우 상태 정보 저장
      • 이벤트 핸들러 생성
    • 외부에서 핸들 접근하기
  • Next.

Script from F.R.I.D.A.Y


들어가며copy^

 이 포스트는 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 생성copy^

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

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

 

헤더 생성copy^

 먼저 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(...)copy^

 굉장히 많은 정보가 인자로 들어가 있습니다. 첫 인자는 윈도우 타이틀에 보일 이름, 두 번째 인자는 윈도우의 스타일을 지정하는 인자입니다. 결국 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(...)copy^

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

 

윈도우 상태 정보 저장copy^

 먼저 윈도우 상태를 가리킬 수 있는 포인터를 하나 생성합니다. 윈도우 상태 정보는 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 클래스는 실제로 이벤트 핸들링[1]을 하지 않습니다. 윈도우 프로시저에서 저장한 DERIVED_TYPE이 가진 멤버 함수를 이벤트 핸들러로 이용할 겁니다.[2]

 

이벤트 핸들러 생성copy^

 실제 메시지 처리는 이벤트 핸들러 메서드에서 처리할 것입니다. 그러나 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);
}

 

 

외부에서 핸들 접근하기copy^

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

HWND Window() const {
    return hWnd;
}

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

 


Next.copy^

 

Win32 MainWindow Class 생성하기

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

pang2h.tistory.com

 

728x90
반응형