Introduction

Hello, and welcome to Introduction to WinAPI. We are at lesson 1, the starting point of the course. In this lesson, we will set up the fundamentals of a native Windows program.

By the end of this lesson, you will understand how to establish the architectural foundation of a native Windows program, linking your application's unique identity with a behavioral blueprint to form the backbone of an event-driven system. Let's dive in!

The Entry Point

The entry point is where your application meets the Operating System. If you have written C++ before, you are likely familiar with int main(). This is the entry point for "Console" applications. In a console app, the program starts, executes logic linearly, and ends.

Windows programs work differently. They are event-driven. Instead of running in a straight line, the program sits in a loop and waits for the operating system to tell it something happened (like a mouse click). To support this, Windows requires two things to happen before anything appears on the screen:

  1. The Entry Point (WinMain): The operating system needs a specific function to start your process and provide it with an Instance Handle (hInstance). This handle represents the base memory address where your program's executable (the "module") is loaded.
  2. The Blueprint (Window Class): Windows is a multi-tasking OS. It needs to know how to handle your window's behavior before it creates it. The "Window Class" is a structure where you tell Windows: "For every window I create of this type, use this icon, this cursor, and most importantly, send all its messages to this specific function."

These are connected because you cannot register a blueprint (Window Class) without the handle (hInstance) provided by WinMain.

Understanding WinAPI Data Types

When writing native Windows applications, you aren't working within a managed "bubble" like you would in Java or C#. WinAPI serves as the primary user-mode interface between your application and the Windows subsystems. These libraries allow your code to request services from the OS, such as creating windows or drawing graphics. To ensure this communication is seamless across different hardware, Windows defines its own data types—mostly typedef aliases that keep your code compatible with the OS:

  • UINT: An unsigned int. Used frequently for message codes.
  • WORD / DWORD: 16-bit and 32-bit ("Double Word") unsigned integers. Used for flags and bitmasks.
  • LPSTR / LPWSTR: "Long Pointer to [Wide] String." These are char* (ANSI) and wchar_t* (Unicode) pointers.
  • HANDLE: An opaque identifier. Types starting with H (like HWND or HINSTANCE) are handles—unique IDs the OS uses to reference resources like windows or icons.
Calling Macros

Beyond data types, we must define how functions are executed. Because we are not within a managed "bubble", when the OS calls your code (or vice-versa), both sides must agree on how to pass arguments and clean up the stack. Macros like WINAPI and CALLBACK specify the Calling Convention (usually __stdcall).

While these macros often resolve to the same underlying compiler instruction, they are used to indicate the "direction" of the call:

  • WINAPI: Used for the system entry point (WinMain) and API functions you call from the Windows libraries. It signals a standard system-to-application transition.
  • CALLBACK: Used for functions you write that the OS calls back later to notify you of events (like the WindowProc).

These conventions prevent stack corruption and crashes by ensuring the caller and the callee agree on who is responsible for cleaning up the function's data.

ANSI vs. Wide (Unicode)

Finally, we must match the OS's character encoding. WinAPI functions usually come in two versions:

  • ANSI (A suffix): Uses 8-bit char strings. This is the older, legacy format.
  • Wide (W suffix): Uses 16-bit wchar_t (UTF-16) strings.

In this course, we use the Wide versions (like WNDCLASSEXW) because modern Windows handles everything as Unicode internally. By using Wide strings directly, we prevent the OS from having to perform hidden conversions, making your application faster and more compatible with international characters.

Adding Windows Types And The Message Handler Declaration

Before we start writing the entry point, we must declare the function that will process messages—this is known as the Window Procedure.

The #include <windows.h> header is the gateway to the WinAPI. It is a massive header that includes many other sub-headers, providing the definitions for all the functions, macros, and data types required to interact with the Windows subsystem.

In this snippet, we encounter several specific Windows data types:

  1. LRESULT: This is the return type for all window procedures. It is essentially a long integer that the program returns to the OS after processing a message to indicate the result of the operation.
  2. CALLBACK: This is a calling convention macro. It tells the compiler how to handle the function's arguments on the stack. Because the Operating System (not your code) is the one that calls this function, the CALLBACK convention ensures the OS and your program speak the same language.
  3. HWND: This stands for "Handle to a Window." In Windows, a "Handle" is an opaque identifier (like a pointer or a unique ID) used to reference an object managed by the OS.
  4. UINT uMsg: An unsigned integer that represents the message code (e.g., "a key was pressed" or "the window was resized").
  5. WPARAM and LPARAM: These are message parameters that carry extra information. For example, if the message is "a key was pressed," these parameters contain additional data about the message (e.g., which key was pressed).
The WinMain Entry Point

With the forward declaration completed, we can move on to the entry point of our application. Instead of main, we define WinMain. This is where the OS hands control to your code.

The parameters are provided by the OS:

  1. hInstance: The base address of the module (this executable) in memory. You will need this to register your window class.
  2. hPrevInstance: A legacy parameter (always NULL in modern Windows).
  3. lpCmdLine: The command-line arguments as a string.
  4. nCmdShow: A flag indicating if the window should start minimized, maximized, or normal.
Defining The Window Class (The Blueprint)

Inside WinMain, we don't immediately create a window. First, we must define the "type" of window we want. In Windows, this is done using the WNDCLASSEXW structure. Think of this as a template or a blueprint. The OS needs this template so that if you create ten different windows of this class, it knows they should all share the same icon, the same background color, and the same logic.

First, we define CLASS_NAME. The L prefix is crucial; it tells the compiler to treat the string as Wide characters (UTF-16). This name acts as a unique identifier for our blueprint within the operating system.

Next, we declare the WNDCLASSEXW structure. The EX stands for "Extended," which is the modern version of the original window class structure. We initialize it with { 0 } to ensure every single member of the structure is set to null or zero. This prevents "garbage" memory values from causing unpredictable behavior. Finally, we set wc.cbSize. Windows uses this value to verify which version of the structure you are using, ensuring forward and backward compatibility.

Filling The Class Properties

Now we fill in the specific details that define how windows of this class will look and act. This is where we link the application instance (the "Who") with the logic (the "How").

Let's break down these members:

  1. lpfnWndProc: This is a "Long Pointer to a Function." We assign it the name of our pre-declared WindowProc function. This tells Windows: "Whenever a user clicks or types in a window of this class, run the code inside WindowProc."
  2. hInstance: This is the handle passed to us by WinMain, identifying the module that contains the window procedure.
  3. lpszClassName: This assigns our string ID ("SampleWindowClass") to the structure. Later, when we want to create a window, we will just give the OS this string name, and it will look up this blueprint.
  4. hCursor: We use LoadCursor to define what the mouse looks like when it hovers over our window. IDC_ARROW is the standard pointer.
  5. hbrBackground: This defines the background color. is a system-defined constant. The is a legacy WinAPI requirement used to distinguish between a color index and a handle.
Registering The Window Class

Even though we have filled out the structure, the Operating System doesn't know it exists yet. It is currently just a piece of local data inside our WinMain function. To make it "official," we must register it.

The RegisterClassExW function takes the address of our blueprint and copies that information into an internal system table managed by the Windows windowing subsystem. If the registration fails (for example, if the name is already taken), the function returns zero. If it succeeds, the OS is now ready to manufacture actual windows based on this blueprint. At this stage, our code simply registers the class and exits, as we haven't yet told Windows to actually "Show" a window.

Creating The Window Handle

Now we reach the center of the lesson: asking the operating system to create a real window. The return value is an HWND, which is our handle to the new window. This handle is how we will talk to the window from this point on.

These first arguments define the identity and basic appearance of the window:

  1. 0 means we are not requesting any special extended style.
  2. CLASS_NAME tells Windows which registered class to instantiate.
  3. L"WinAPI Introduction" becomes the title bar text.
  4. WS_OVERLAPPEDWINDOW gives us a normal desktop window with a title bar, border, and standard controls.

This is a good example of how WinAPI works: we do not construct a C++ object ourselves; instead, we provide parameters and let the operating system allocate the actual window resource.

Position, Size, And Ownership

The rest of the creation call fills in where the window should appear, how large it should be, and whether it belongs to another window. After the call, we immediately check whether creation succeeded, because a NULL handle means Windows could not build the window.

Here, CW_USEDEFAULT lets Windows choose a reasonable starting position and size for us, which is helpful while learning. Both NULL values for parent and menu mean this is a stand-alone top-level window with no attached menu. A top-level window is one that is not contained within another window (its parent is essentially the desktop itself), allowing it to move freely and appear on the taskbar. Passing hInstance again tells Windows which application owns the new window. The final NULL means we are not sending any extra custom data during creation. If hwnd comes back as NULL, we stop immediately because there is no valid window to show.

Showing The Window

Creating a window does not automatically make it visible. Windows gives us the resource first, then waits for us to decide when and how to show it. That is why the next two calls matter: one controls visibility, and the other requests an immediate paint.

ShowWindow uses nCmdShow from WinMain so the system can suggest whether the window should open normally, minimized, or in another display state.

UpdateWindow asks Windows to send a paint message right away if one is needed. In WinAPI terminology, "painting" refers to the process of rendering pixels—drawing the window's background, text, and graphics onto the screen. When a window is first created or uncovered by another window, the OS informs your program that a specific area is "dirty" and needs to be "painted" (redrawn). UpdateWindow ensures this happens immediately so the user doesn't see a blank or flickering box.

Important Note: To keep the program alive for this lesson, we've added a while(1); loop. This is intentionally broken throwaway code. In a real-world scenario, this is a "busy-loop" that consumes 100% of a CPU core and prevents the window from processing messages, making it appear "Not Responding." We use it here only as a temporary placeholder. In the next lesson, we will replace it with a proper message loop, which is the correct way to keep an application running and responsive.

Providing A Default Window Procedure

The final piece is the WindowProc. Since we told the OS this function would handle messages, we must define it.

Right now, we don't have any custom logic (like closing the app when an 'X' is clicked). To keep the program stable, we pass all messages to DefWindowProcW. This is a built-in Windows function that performs the "default" action for every message.

Conclusion and Next Steps

In this lesson, we established the fundamental handshake between our code and the Windows operating system. We replaced the standard main with WinMain to obtain the application's hInstance, defined a behavioral blueprint using the WNDCLASSEXW structure, and registered it with the windowing subsystem. Finally, we used that blueprint to request a window handle (HWND) from the OS and commanded the system to display the window on the screen.

Our architecture is now in place, but the window is currently non-responsive because we are using a temporary placeholder loop. In the next lesson, we will implement the Message Loop, the engine that enables your program to process events like clicks and keystrokes in real-time.

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal