Working with the Win32 API

Working with the Win32 API

Main Window Requirements

  1. Window Procedure Function
  2. Main Window Function
  3. Window Class (not a c++ class)
  4. Message Loop
  5. Resource file (optional)

The windows API uses a LOT of preprocessor instructions to modify function definitions that can be very confusing at first. Also most of the interactions with the API will be through Macros rather than functions.

First things first

Text encoding should be UNICODE as that is the standard these days. To enable unicode you will need to add two define statements:

#define UNICODE
#define _UNICODE

Without these define statements, either in the code or as a compiler flag the wrong functions might be called. When using an IDE put them in the code, and make sure there are no automatic defines assumed by the IDE so as not to show confusing results with code paths.

Windows Procedure Function

The window procedure needs to be defined, or at least declared prior to the main window function. The signature is:

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM)

LRESULT is a window API typedef
CALLBACK expands to __stdcall and is absolutely required for this function to work

For the main window procedure, you should pass on any unprocessed messages to the default windows procedure:

return DefWindowProc(hand, msg, wParam, lParam);

Main Window Function

The main window function is a macro and the inclusion of the header file <windows.h> causes the compiler to use a different entry point (not main) and will prepare and call the WinMain function with the listed parameters.

Int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, INT nCmdShow)

APIENTRY expads to __stdcall and is required
hInstance is a handle to the module (.exe)
hPrevInstance is a hold over from the win16 api and is no longer used
lpCmdLine is a string pointer containing the command line args
nCmdShow is an int containing command line info regarding how the window should be show on start, useful for shortcut configurations

Window Class

The window class is a STRUCT and needs to be filled out completely prior to being registered with the system. Incorrect details will cause the class registration to fail…

Use WNDCLASSEX (extended)

See example program at the bottom.

Message Loop

The message loop is blocking and runs for the entire time the window executable is running.

Messages are obtained from the system, and then translated and distributed to the running application. Note the while loop for this, the logical comparison > 0 is important here, as negative values here would indicate an error so don’t use != 0 or leave it off.

Resource File

Using a resource file will allow the use of a manifest and also greatly simplify loading resources. See useful website references for details.

The file should be resource.rc

It needs to be compiled with the program objects.

To get an object compiled from a resource file you need to use a program called winders:

windres -i resource.rc -o resource.o

Include resources in the program like this:

MAKEINTRESOURCE(IDI_APPICON)

Or depending on the use case just access the custom resource directly

IDI_APPICON

IDI_APPICON in this case is a custom defined resource and will be in the resource.h file

Example Program

This is an example of a minimal program from which you could build off:

#define UNICODE
#define _UNICODE

#include <windows.h>

const wchar_t className[] = TEXT("WindowClass");

LRESULT CALLBACK WindowProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR lpCmdLine, INT nCmdShow)
{
    WNDCLASSEX wc;
    HWND hWnd;
    MSG msg;

    wc.cbClsExtra = 0;
    wc.cbSize = sizeof(WNDCLASSEX);
    wc.cbWndExtra = 0;
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wc.hInstance = hInstance;
    wc.lpfnWndProc = WindowProc;
    wc.lpszClassName = className;
    wc.lpszMenuName = NULL;
    wc.style = 0;

    // Register the window class
    if (!RegisterClassEx(&wc))
    {
        MessageBox(NULL, TEXT("Could not register the window class"), TEXT("Error"), MB_OK | MB_ICONERROR);
        return 1;
    }

    // Create the window
    hWnd = CreateWindowEx(
        0,
        className,
        TEXT("Window Title"),
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, 400, 300,
        NULL, NULL, hInstance, NULL);

    if (!hWnd)
    {
        MessageBox(NULL, TEXT("Could not create window"), TEXT("Error"), MB_OK | MB_ICONERROR);
        return 1;
    }

    // Show the window
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    // Handle the message loop
    while (GetMessage(&msg, hWnd, 0, 0) > 0)
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (INT)msg.lParam;
}

LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    }

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

I found a lot of useful resources while figuring out how to work with the Win32 API:

Official Microsoft Documentation
Some GUI Examples
Minimizing C++ Win32 App To System Tray
Building Win32 GUI apps with MinGW
The Forgers Win32 API Programming Tutorial