When a Windows program calls something innocuous like CreateFileW, an impressive amount of machinery springs into action between your line of C++ and the disk controller. Understanding that machinery — even at a high level — pays off the first time you have to debug a strange hang, write a driver, or audit a piece of low-level code. This post walks through the layers, from the friendly Win32 API you already use to the native NT system calls underneath.

The three layers you actually touch

For practical purposes there are three layers between user code and the kernel:

Following CreateFile down the stack

Let's trace a single call. In Win32, you'd write:

// Win32 — the layer most apps target.
HANDLE h = CreateFileW(
    L"C:\\temp\\hello.txt",
    GENERIC_READ,
    FILE_SHARE_READ,
    nullptr,
    OPEN_EXISTING,
    FILE_ATTRIBUTE_NORMAL,
    nullptr);

Inside kernel32.dll, that path is normalized (drive letters resolved, \\?\ prefix added) and the call is forwarded to NtCreateFile in ntdll.dll:

// Native API — what kernel32 actually calls under the hood.
extern "C" NTSTATUS NTAPI NtCreateFile(
    PHANDLE             FileHandle,
    ACCESS_MASK         DesiredAccess,
    POBJECT_ATTRIBUTES  ObjectAttributes,
    PIO_STATUS_BLOCK    IoStatusBlock,
    PLARGE_INTEGER      AllocationSize,
    ULONG               FileAttributes,
    ULONG               ShareAccess,
    ULONG               CreateDisposition,
    ULONG               CreateOptions,
    PVOID               EaBuffer,
    ULONG               EaLength);

NtCreateFile sets up a handful of registers and executes a syscall. Control transfers into the kernel, the I/O manager builds an IRP (I/O Request Packet), routes it to the file system driver, which in turn talks to a storage driver. The handle you get back is just an index into your process's handle table.

Calling the Native API directly

You can skip the Win32 layer entirely. There's almost never a reason to do so in application code, but it's instructive to try once. Here's a minimal sample that reads the system's process list using NtQuerySystemInformation:

// Resolve the function from ntdll at runtime.
using NtQuerySystemInformation_t = NTSTATUS(NTAPI*)(
    ULONG, PVOID, ULONG, PULONG);

HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
auto NtQuerySystemInformation =
    (NtQuerySystemInformation_t)GetProcAddress(
        ntdll, "NtQuerySystemInformation");

ULONG needed = 0;
NtQuerySystemInformation(5 /* SystemProcessInformation */,
                         nullptr, 0, &needed);
Heads up. The Native API is documented but mostly considered "internal." Microsoft reserves the right to change behavior between Windows releases. Use it for tooling and research; prefer Win32 for shipping software.

How handles really work

A handle isn't a pointer — it's an opaque index into a per-process handle table managed by the Object Manager. When you call CloseHandle, the kernel decrements a reference count on the underlying object. When it hits zero, the object is freed. This is why leaking handles is so easy: nothing in your address space looks broken when it happens.

Tools that make this visible

Where to go next

A few directions if this whetted your appetite: write a tiny tool with the Native API, walk the PEB to enumerate loaded modules, or read the Windows Internals book series. Once you've seen the full path from CreateFile to the disk, every weird platform bug becomes a little less mysterious.

Want help going deeper? Our coaching tracks include a Windows internals module with hands-on exercises in WinDbg. Get in touch to join a cohort.