본문 바로가기

Win32 API

18. Win32 API - Win32 클래스화(2 - 설명)

1. 실행 흐름 도식화

작업 흐름

실행 흐름에 대한 이해를 돕는 도식화 입니다.

 

2. WinMain

// Win32_Study.cpp : 애플리케이션에 대한 진입점을 정의합니다.
//

#include "pch.h"
#include "framework.h"
#include "Win32_Study.h"
#include "CustomWinApp.h"

int WINAPI wWinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPWSTR pszCmdLine,
    int nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(pszCmdLine);
    UNREFERENCED_PARAMETER(nCmdShow);

    HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0);

    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (SUCCEEDED(hr))
    {
        {
            CustomWinApp app;
            hr = app.Initialize(hInstance);
            if (SUCCEEDED(hr))
            {
                // Main message loop:
                MSG msg;
                while (GetMessage(&msg, NULL, 0, 0))
                {
                    TranslateMessage(&msg);
                    DispatchMessage(&msg);
                }
            }
        }

        CoUninitialize();
    }

    return 0;
}
Win32 데스크톱 애플리케이션이 실행될 때 winMain 함수는 가장 먼저 실행되는 함수입니다.
우선 Direct2D는 COM(Component Object Model)로 이루어져 있으므로 COM 라이브러리를 초기화합니다.
그 뒤 Window App을 초기화 하고 메시지 루프에서 메시지 큐의 메시지를 처리하는 과정을 반복합니다.
메시지루프가 종료되면 이후 COM 라이브러리를 종료합니다.

 

3. COM 객체 초기화

HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
Direct2D를 사용하기 위해서는 COM 라이브러리를 사용해야 합니다.
COM 라이브러리를 초기화 합니다.

 

4. Window Procedure 선언

class CustomWinApp
{
public:
	// ... 생략	

private:
    // WndProc 함수.
	LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
	static LRESULT CALLBACK s_WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

	//... 생략
}
위의 코드는 Window Procedure를 선언하는 부분입니다.

 

5. WindowClass 등록

HRESULT CustomWinApp::Initialize(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = s_WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_WIN32STUDY));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = 0;
    wcex.lpszClassName = TEXT("WIN32_STUDY");
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

    HRESULT hr = (RegisterClassExW(&wcex) == 0) ? E_FAIL : S_OK;
 	// ... 생략   
}
Window Class를 등록하는 과정입니다.
WNDCLASSEXW 구조체를 선언하고 필요한 인자값을 채워나갑니다.
4번과 5번 과정을 수행하다 보면 Window Class 구조체의 lpfnWndProc(Window Procedure 인자)에
정적 멤버 함수인 s_WndProc을 할당하는 것을 볼 수 있습니다.
멤버 함수인 WndProc를 바로 할당하지 않고 정적 멤버 함수인 s_WndProc를 할당하는데에는 이유가 존재합니다.

 

클래스 등록 시 정적 멤버함수가 필요한 이유

WNDCLASSEXW 구조체

Window Class 구조체를 확인하게 되면 lpfnWndProc 인자가 WNDPROC 타입인 것을 확인할 수 있습니다.
WNDPROC는 어떤 타입일까요?

 

 

WNDPROC 타입

WNDPROC는 typedef 로 정의된 반환 타입이 LRESULT이고 호출 규칙이 __stdcall(CALLBACK)인 함수포인터입니다.
위를 보면 멤버 함수 WndProc는 LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)으로 선언되어 있어서 문제 없이 할당할 수 있을 것으로 보여집니다.

 

WndProc
s_WndProc

하지만 멤버 함수 WndProc를 lpfnWndProc 인자에 할당했을 때는 에러가 발생합니다.
정적 멤버 함수를 lpfnWndProc 인자에 할당했을 때에는 문제 없이 할당이 이루어 집니다.

Win32의 구조를 클래스화를 통해 분할하기 위해서는 멤버 함수의 활용이 필수적입니다.
하지만 윈도우 클래스의 lpfnWndProc 인자에 Window Procedure를 등록하기 위해서는 
일반 함수 포인터에 들어갈 함수 주소가 필요합니다.

Win32 구조를 클래스화 하기 위해서 Window Procedure를 멤버 함수로 정의했는데,
멤버 함수 주소는 일반 함수 포인터에 할당할 수 없는 모순이 일어납니다.

 

static member functions

static members - cppreference.com

 

static members - cppreference.com

Inside a class definition, the keyword static declares members that are not bound to class instances. Outside a class definition, it has a different meaning: see storage duration. [edit] Syntax A declaration for a static member is a member declaration whos

en.cppreference.com

cppreference 내용을 확인하게 되면 아래의 문장을 확인할 수 있습니다.
The address of a static member function may be stored in a regular pointer to function,

but not in a pointer to member function
정적 멤버함수 주소는 일반 함수 포인터에 할당될 수 있다는 내용입니다.
위의 모순점을 정적 멤버 함수를 통해서 해결할 수 있습니다. 

 

s_WndProc

LRESULT CALLBACK CustomWinApp::s_WndProc(
    HWND hWnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam)
{
    CustomWinApp* pThis = NULL;
    LRESULT lRet = 0;

    if (uMsg == WM_NCCREATE)
    {
        // 윈도우 생성 정보에 CustomWinApp 에 대한 정보를 등록합니다.
        LPCREATESTRUCT pcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
        pThis = reinterpret_cast<CustomWinApp*>(pcs->lpCreateParams);

        // 윈도우 인스턴스에 추가된 4바이트 CustomWinApp 객체의 주소를 저장합니다.
        SetWindowLongPtr(hWnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(pThis));
        lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    else
    {
        pThis = reinterpret_cast<CustomWinApp*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
        if (pThis)
        {
            lRet = pThis->WndProc(hWnd, uMsg, wParam, lParam);
        }
        else
        {
            lRet = DefWindowProc(hWnd, uMsg, wParam, lParam);
        }
    }

    return lRet;
}
WM_NCCREATE 메시지는 창을 처음 만들 때 WM_CREATE 메시지 이전에 전달되는 메시지입니다.
여기서 윈도우 생성 정보에 만들어둔 WindowApp 정보를 등록합니다.
정적 멤버함수는 this pointer가 없기 때문에 pThis 포인터에 CustomWinApp의 주소를 넣어둡니다.
그리고 WM_NCCREATE 외의 다른 메시지가 전달될 때는 pThis 포인터를 이용하여 WndProc 멤버함수를 호출합니다.

 

WndProc

LRESULT CustomWinApp::WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_PAINT:
    {
        OnPaint();
        return 0;
    }
    case WM_CREATE:
    {
        SetHandle(hWnd);
        OnCreate();
        return 0;
    }
    case WM_TIMER:
    {
        OnTimer(wParam);
        return 0;
    }
    case WM_DESTROY:
        OnDestroy();
    }

    return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
실질적인 윈도우 메시지를 처리하는 WindowProcedure 멤버 함수입니다.
정적 멤버함수로부터 WM_NCCREATE 외의 다른 메시지가 발생하였을 때 처리할 방법을 정의합니다.

 

6. Direct 2D Factory, WIC 객체 생성

HRESULT CustomWinApp::Initialize(HINSTANCE hInstance)
{
	// ... 생략
	if (SUCCEEDED(hr))
    {
        // Create D2D factory
        hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, &m_pD2DFactory);
    }

    if (SUCCEEDED(hr))
    {
        // Create WIC factory
        hr = CoCreateInstance(
            CLSID_WICImagingFactory,
            NULL,
            CLSCTX_INPROC_SERVER,
            IID_PPV_ARGS(&m_pIWICFactory));
    }
    
    //... 생략
}
Direct2D 리소스를 사용하기 위한 Factory 객체를 생성합니다.
이미지 파일을 관리하기 위한 COM 객체인 WIC를 멤버 변수에 할당합니다.

 

7. Window 생성

HRESULT CustomWinApp::Initialize(HINSTANCE hInstance)
{
	//... 생략
    if (SUCCEEDED(hr))
    {
        // Create window
        m_hWnd = CreateWindow(
            TEXT("WIN32_STUDY"),
            TEXT("WIN32_STUDY_WINDOW"),
            WS_OVERLAPPEDWINDOW | WS_VISIBLE,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            NULL,
            NULL,
            hInstance,
            this);
        hr = (m_hWnd == NULL) ? E_FAIL : S_OK;
    }
    //... 생략
    
 }

 

https://learn.microsoft.com/en-us/windows/win32/learnwin32/managing-application-state-

 

Managing Application State - Win32 apps

A window procedure is just a function that gets invoked for every message, so it is inherently stateless. Therefore, you need a way to track the state of your application from one function call to the next.

learn.microsoft.com

CreateWindow의 마지막 매개변수에 this포인터 인수를 넣어주는 이유는
Window가 생성될 때 Window Procedure가 호출 되는데
이 때 lParam에서 현재 클래스의 포인터를 가져올 수 있기 때문입니다.

LPCREATESTRUCT pcs = reinterpret_cast<LPCREATESTRUCT>(lParam);
pThis = reinterpret_cast<CustomWinApp*>(pcs->lpCreateParams);
위의 두 줄의 코드를 이용해서 pThis 변수에 CustomWinApp 인스턴스의 포인터를 담을 수 있습니다.

 

8. 참조

static members - cppreference.com

 

static members - cppreference.com

Inside a class definition, the keyword static declares members that are not bound to class instances. Outside a class definition, it has a different meaning: see storage duration. [edit] Syntax A declaration for a static member is a member declaration whos

en.cppreference.com

https://learn.microsoft.com/en-us/windows/win32/learnwin32/managing-application-state-

 

Managing Application State - Win32 apps

A window procedure is just a function that gets invoked for every message, so it is inherently stateless. Therefore, you need a way to track the state of your application from one function call to the next.

learn.microsoft.com

https://blog.naver.com/tipsware/221133045626

 

Win32 프로그램을 클래스화 하기 - 4단계

:   Win32 프로그래밍 관련 전체 목차 http://blog.naver.com/tipsware/221059977193...

blog.naver.com

 

'Win32 API' 카테고리의 다른 글

17. Win32 API - Win32 클래스화(1 - 전체 코드)  (0) 2024.03.23
16. Win32 API - Direct2D  (0) 2024.03.16
15. Win32 API - GDI+  (0) 2024.03.16
14. Win32 API - StockObject  (0) 2024.03.09
13. Win32 API - 윈도우 좌표  (0) 2024.03.09