Win32 API programming with C - Managing Application State

A window procedure is essentially a function called for every message, lacking inherent state. To manage your application's state across calls, one basic method is using global variables.

However, for larger applications, this can lead to too many global variables. Additionally, if your application has multiple windows, each with its distinct window procedure, managing and associating the correct variables with the correct window can become complex and prone to mistakes.

The CreateWindowEx function provides a way to pass any data structure to a window. When this function is called, it sends the following two messages to your window procedure:

  • WM_NCCREATE

  • WM_CREATE

These messages are sent in the order listed.

The WM_NCCREATE and WM_CREATE message are sent before the window becomes visible. That makes them a good place to initialize your UI.

The last parameter of CreateWindowEx is a pointer of type void*. You can pass any pointer value that you want in this parameter. When the window procedure handles the WM_NCCREATE or WM_CREATE message, it can extract this value from the message data.

First, we need to define a structure that holds state information.

typedef struct {
    char data[10];
} StateInfo;

When we call CreateWindowEx, we pass a pointer to this structure in the final void* parameter.

StateInfo* pState = (StateInfo*)GlobalAlloc(GMEM_FIXED, sizeof(StateInfo));
CopyMemory(pState->data, "myAppData", 9);

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

HWND hwnd;
hwnd = CreateWindowEx(
    0,                      // Optional window styles.
    g_szClassName,          // Window class
    "My window",            // Window text
    // ...
    hInstance,              // Instance handle
    pState                  // Additional application data
);

When we receive the WM_NCCREATE and WM_CREATE messages, the lParam parameter of each message is a pointer to a CREATESTRUCT structure. The CREATESTRUCT structure, in turn, contains the pointer that we passed into CreateWindowEx.

Here's how we extract the pointer from the data structure.

CREATESTRUCT* pCreate = (CREATESTRUCT*)(lParam);
pState = (StateInfo*)(pCreate->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);

The purpose of SetWindowLongPtr function call is to store the StateInfo pointer in the instance data for the window. We can always get the pointer back from the window by calling GetWindowLongPtr.

Each window has its own instance data, so you can create multiple windows and give each window its own instance of the data structure.

Now we can rewrite our window procedure as follows:

StateInfo* GetAppState(HWND hwnd)
{
    LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
    StateInfo* pState = (StateInfo*)(ptr);
    return pState;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    StateInfo* pState;
    if (msg == WM_CREATE)
    {
        CREATESTRUCT* pCreate = (CREATESTRUCT*)(lParam);
        pState = (StateInfo*)(pCreate->lpCreateParams);
        SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
    }
    else
    {
        pState = GetAppState(hwnd);
    }
    switch (msg)
    {
    // ...
    }
    return 0;
}

https://learn.microsoft.com/en-us/windows/win32/learnwin32/your-first-windows-program