다음을 통해 공유


애플리케이션 상태 관리

창 프로시저는 모든 메시지에 대해 호출되는 함수이므로 기본적으로 상태 비저장입니다. 따라서 함수 호출 사이에 애플리케이션의 상태를 추적하는 방법이 필요합니다.

가장 간단한 접근 방식은 전역 변수에 모든 항목을 넣는 것입니다. 이 접근 방식은 소규모 프로그램에 충분히 제대로 적용되며 많은 SDK 샘플에서 이 접근 방식을 사용합니다. 그러나 큰 프로그램에서는 전역 변수가 많이 사용됩니다. 또한 창이 여러 개 있을 수 있으며 각각 고유한 창 프로시저가 있습니다. 어떤 창이 어떤 변수에 액세스해야 하는지 추적하는 것은 복잡하고 오류가 발생하기 쉽습니다.

CreateWindowEx 함수는 모든 데이터 구조를 창에 전달하는 방법을 제공합니다. 이 함수가 호출되면 창 프로시저에 다음 두 메시지를 전송합니다.

이 메시지는 나열된 순서대로 전송됩니다. CreateWindowEx 중에 이 두 메시지만 전송되는 것은 아니지만 여기서는 다른 메시지를 무시해도 됩니다.

WM_NCCREATEWM_CREATE 메시지는 창이 표시되기 전에 전송됩니다. 따라서 창의 초기 레이아웃을 결정하는 경우처럼 UI를 초기화하는 데 위치입니다.

CreateWindowEx의 마지막 매개 변수는 void*형식의 포인터입니다. 이 매개 변수에서 원하는 포인터 값을 전달할 수 있습니다. 창 프로시저가 WM_NCCREATE 또는 WM_CREATE 메시지를 처리하면 메시지 데이터에서 이 값을 추출할 수 있습니다.

이 매개 변수를 사용하여 애플리케이션 데이터를 창에 전달하는 방법을 살펴보겠습니다. 먼저 상태 정보를 보유하는 클래스 또는 구조체를 정의합니다.

// Define a structure to hold some state information.

struct StateInfo {
    // ... (struct members not shown)
};

CreateWindowEx를 호출할 때 최종 void* 매개 변수에서 이 구조체에 대한 포인터를 전달합니다.

StateInfo *pState = new (std::nothrow) StateInfo;

if (pState == NULL)
{
    return 0;
}

// Initialize the structure members (not shown).

HWND hwnd = CreateWindowEx(
    0,                              // Optional window styles.
    CLASS_NAME,                     // Window class
    L"Learn to Program Windows",    // Window text
    WS_OVERLAPPEDWINDOW,            // Window style

    // Size and position
    CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,

    NULL,       // Parent window    
    NULL,       // Menu
    hInstance,  // Instance handle
    pState      // Additional application data
    );

WM_NCCREATEWM_CREATE 메시지를 받으면 각 메시지의 lParam 매개 변수는 CREATESTRUCT 구조체에 대한 포인터입니다. CREATESTRUCT 구조체에는 CreateWindowEx에 전달한 포인터가 포함됩니다.

createstruct 구조체의 레이아웃을 보여 주는 다이어그램

다음은 데이터 구조에 대한 포인터를 추출하는 방법입니다. 먼저 lParam 매개 변수를 캐스팅하여 CREATESTRUCT 구조체를 가져옵니다.

CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);

CREATESTRUCT 구조체의 lpCreateParams 멤버는 CreateWindowEx에서 지정한 원래 void 포인터입니다. lpCreateParams를 캐스팅하여 사용자 고유의 데이터 구조에 대한 포인터를 가져옵니다.

pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);

다음으로 SetWindowLongPtr 함수를 호출하고 데이터 구조에 대한 포인터를 전달합니다.

SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);

이 마지막 함수 호출의 목적은 창에 대한 인스턴스 데이터에 StateInfo 포인터를 저장하는 것입니다. 이렇게 하면 GetWindowLongPtr을 호출하여 항상 창에서 포인터를 다시 가져올 수 있습니다.

LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);

각 창에는 자체 인스턴스 데이터가 있으므로 여러 창을 만들고 각 창에 데이터 구조의 자체 인스턴스를 제공할 수 있습니다. 이 접근 방식은 사용자 지정 컨트롤 클래스를 만드는 경우처럼 창의 클래스를 정의하고 해당 클래스의 창을 두 개 이상 만드는 경우에 특히 유용합니다. 작은 도우미 함수에서 GetWindowLongPtr 호출을 래핑하는 것이 편리합니다.

inline StateInfo* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
    return pState;
}

이제 다음과 같이 창 프로시저를 작성할 수 있습니다.

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    StateInfo *pState;
    if (uMsg == WM_CREATE)
    {
        CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
        pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
    }
    else
    {
        pState = GetAppState(hwnd);
    }

    switch (uMsg)
    {


    // Remainder of the window procedure not shown ...

    }
    return TRUE;
}

개체 지향 접근 방식

이 접근 방식을 더 확장할 수 있습니다. 창에 대한 상태 정보를 저장할 데이터 구조를 이미 정의했습니다. 이 데이터 구조에 데이터에서 작동하는 멤버 함수(메서드)를 제공하는 것이 좋습니다. 이렇게 하면 자연스럽게 구조체(또는 클래스)가 창의 모든 작업을 담당하는 디자인으로 이어집니다. 그러면 창 프로시저가 클래스의 일부가 됩니다.

즉, 다음에서 이동할 수 있습니다.

// pseudocode

LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    StateInfo *pState;

    /* Get pState from the HWND. */

    switch (uMsg)
    {
        case WM_SIZE:
            HandleResize(pState, ...);
            break;

        case WM_PAINT:
            HandlePaint(pState, ...);
            break;

       // And so forth.
    }
}

값:

// pseudocode

LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
        case WM_SIZE:
            this->HandleResize(...);
            break;

        case WM_PAINT:
            this->HandlePaint(...);
            break;
    }
}

유일한 문제는 MyWindow::WindowProc 메서드를 연결하는 방법입니다. RegisterClass 함수는 창 프로시저가 함수 포인터가 될 것으로 예상합니다. 이 컨텍스트에서는 (비정적) 멤버 함수에 대한 포인터를 전달할 수 없습니다. 그러나 정적 멤버 함수에 대한 포인터를 전달한 다음, 멤버 함수에 위임할 수 있습니다. 다음은 이 접근 방식을 보여 주는 클래스 템플릿입니다.

template <class DERIVED_TYPE> 
class BaseWindow
{
public:
    static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
    {
        DERIVED_TYPE *pThis = NULL;

        if (uMsg == WM_NCCREATE)
        {
            CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam;
            pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
            SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);

            pThis->m_hwnd = hwnd;
        }
        else
        {
            pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
        }
        if (pThis)
        {
            return pThis->HandleMessage(uMsg, wParam, lParam);
        }
        else
        {
            return DefWindowProc(hwnd, uMsg, wParam, lParam);
        }
    }

    BaseWindow() : m_hwnd(NULL) { }

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

        wc.lpfnWndProc   = DERIVED_TYPE::WindowProc;
        wc.hInstance     = GetModuleHandle(NULL);
        wc.lpszClassName = ClassName();

        RegisterClass(&wc);

        m_hwnd = CreateWindowEx(
            dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
            nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
            );

        return (m_hwnd ? TRUE : FALSE);
    }

    HWND Window() const { return m_hwnd; }

protected:

    virtual PCWSTR  ClassName() const = 0;
    virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;

    HWND m_hwnd;
};

BaseWindow 클래스는 특정 창 클래스가 파생되는 추상 기본 클래스입니다. 예를 들어 다음은 BaseWindow에서 파생된 단순 클래스의 선언입니다.

class MainWindow : public BaseWindow<MainWindow>
{
public:
    PCWSTR  ClassName() const { return L"Sample Window Class"; }
    LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};

창을 만들려면 BaseWindow::Create를 호출합니다.

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
    MainWindow win;

    if (!win.Create(L"Learn to Program Windows", WS_OVERLAPPEDWINDOW))
    {
        return 0;
    }

    ShowWindow(win.Window(), nCmdShow);

    // Run the message loop.

    MSG msg = { };
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return 0;
}

순수 가상 BaseWindow::HandleMessage 메서드는 창 프로시저를 구현하는 데 사용됩니다. 예를 들어 다음 구현은 모듈 1의 시작 부분에 표시된 창 프로시저와 동일합니다.

LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uMsg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        return 0;

    case WM_PAINT:
        {
            PAINTSTRUCT ps;
            HDC hdc = BeginPaint(m_hwnd, &ps);
            FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
            EndPaint(m_hwnd, &ps);
        }
        return 0;

    default:
        return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
    }
    return TRUE;
}

창 핸들은 멤버 변수(m_hwnd)에 저장되므로 매개 변수로 HandleMessage에 전달할 필요가 없습니다.

MFC(Microsoft Foundation Classes) 및 ATL(액티브 템플릿 라이브러리) 같은 기존의 많은 Windows 프로그래밍 프레임워크는 기본적으로 여기에 표시된 것과 유사한 접근 방식을 사용합니다. 물론 MFC와 같은 완전히 일반화된 프레임워크는 비교적 단순한 이 예제보다 더 복잡합니다.

다음

모듈 2: Windows 프로그램에서 COM 사용

BaseWindow 샘플