skip to content

In the Win32 API, what is a HANDLE, which kernel component manages the objects behind handles, and how does a handle differ from a Unix file descriptor?

level: juniorimportance: must knowfreq 58%

answer

  1. one type covers many kernel objects
  2. opaque, and only yours
  3. a table entry, not a pointer
  4. rights are stamped in at open
  5. counted references decide the lifetime

basics

~20 s

A Windows HANDLE is an opaque, per-process entry in that process's handle table referring to an object created by the NT object manager — a file, event, mutex, process, thread or registry key. A Unix file descriptor is a small integer naming a much narrower set of objects.

solid answer

~50 s

On Windows almost everything the kernel owns is an *object* managed by one executive component, the object manager: files, devices, registry keys, processes, threads, events, mutexes, semaphores, timers, sections, tokens. When you open or create one you get back a `HANDLE` — an opaque value indexing your process's private handle table, meaningless in any other process. That uniformity is the practical difference from Unix file descriptors: a descriptor is a small integer covering files, sockets and pipes, whereas one `HANDLE` type covers synchronisation objects and processes too, so `WaitForSingleObject` works on a mutex, a process and a timer alike. The table entry also records the access rights granted when the handle was opened, so the security check happens once rather than per operation. The object manager reference-counts each object; `CloseHandle` drops your reference and the object survives while anyone else holds one. Handles are shared deliberately — marked inheritable and passed by `CreateProcess`, or copied with `DuplicateHandle` — never by guessing a value.

go deeper

for a junior

Know that Win32 calls which open or create something return a HANDLE, that you must call CloseHandle when finished, and that the value is private to your own process.

for a middle

Explain the object manager behind handles: one object type system, a granted-access mask stamped in at open, handle plus pointer reference counts, and a named-object namespace separate from the file system.

for a senior

Show the operational consequences you have hit: a climbing handle count as a leak signature, permission changes not affecting already-open handles, and DuplicateHandle or inheritable handles as the deliberate way to share across processes.

for a principal

Frame handles as the platform's capability model — a handle carries rights granted once — and reason about what that means for privilege boundaries, sandbox design, and which components may hand out duplicated handles at all.

## Everything is an object The NT executive contains a component called the **object manager**, and it is the piece with no single Unix counterpart. Rather than each subsystem inventing its own lifetime, naming and security scheme, the object manager provides one: a generic object header carrying a type, an optional name, a security descriptor, a handle count and a pointer count, followed by a type-specific body. File objects, device objects, registry key objects, process and thread objects, events, mutants (the internal name for mutexes), semaphores, timers, sections (shared memory) and access tokens are all objects of different *types* in this one system. Because of that, the security check, the naming, the lifetime rules and the wait mechanism are written once and apply to everything. ## What a HANDLE actually is A `HANDLE` is not a pointer you can dereference and not a global ID. Each process has a kernel-maintained handle table; a handle is essentially an index into it. The table entry points at the object and records the *granted access mask* — the rights the Security Reference Monitor decided you may exercise when the handle was opened. Two consequences follow immediately, and both come up in interviews: - **The access check happens at open time, not at use time.** `CreateFileW(..., GENERIC_READ, ...)` is checked against the file's DACL once; every later `ReadFile` on that handle merely verifies the granted mask. Changing the file's permissions afterwards does not retroactively invalidate existing handles. - **A handle value means nothing in another process.** Handle 0x1C in your process and handle 0x1C in mine refer to unrelated objects, or to nothing at all. ## Lifetime: two counters, one object The object manager keeps a handle count and a pointer count (kernel-mode references). `CloseHandle` decrements the handle count for your process. The object is destroyed only when *all* references are gone — which is why closing your handle to a mutex another process still holds does not destroy the mutex, and why a running process object outlives every handle to it. Leaking handles is a real production failure mode on Windows: the process's handle count climbs without bound and the objects behind them stay pinned in kernel memory. Long-lived services that open registry keys or events in a loop and forget `CloseHandle` eventually exhaust kernel resources on the whole machine, not just their own address space. ## Sharing a handle Unix processes inherit descriptors across `fork` by default. Windows shares explicitly, in two ways: - Mark the handle inheritable when creating it (the `bInheritHandle` field of `SECURITY_ATTRIBUTES`) *and* pass `TRUE` for `bInheritHandles` to `CreateProcess`. Both conditions are required. - Call `DuplicateHandle`, naming a source process, a source handle and a target process, to place a copy in another process's table — the usual mechanism when the other process already exists. ## The object namespace is not the file system namespace Named objects live in the object manager namespace, a tree of directories separate from `C:\`: `\Device` holds device objects, `\BaseNamedObjects` holds the named mutexes and events applications create, and DOS drive letters are symbolic links resolved into device paths under the global DOS-devices directory. This is why a named mutex has a name with no path on disk, and why applications in different sessions can each create a mutex of the same name without colliding — per-session object directories keep them apart. ## The uniform wait Because synchronisation objects are objects too, `WaitForSingleObject` and `WaitForMultipleObjects` accept any *waitable* handle: a mutex, a semaphore, a timer, an event, a thread or a process. Waiting on a process handle until it becomes signalled is how you wait for a child to exit. Unix has no single call that waits on a mutex and a child process with the same primitive. ## The failure-convention trap One piece of Win32 lore worth carrying into an interview: not every handle-returning call reports failure the same way. `CreateFileW` returns `INVALID_HANDLE_VALUE` on failure, while `CreateEventW`, `CreateMutexW`, `OpenProcess` and `CreateFileMappingW` return `NULL`. Code that checks only for `NULL` after `CreateFileW` will happily use a broken handle. Either way, the actual error code comes from `GetLastError()`.

  • If a process leaks handles, what actually runs out?
    Kernel resources, not the process's address space. Each open handle pins an object in kernel memory and consumes a handle-table entry, so a service that opens events or registry keys in a loop without `CloseHandle` grows its handle count until creation fails — and because the objects sit in kernel pools, the pressure is machine-wide. The symptom is usually failing creates with out-of-resource errors long before the process looks large.
  • Why does changing a file's permissions not stop a process that already has it open?
    Because the access check runs once, at open time. The Security Reference Monitor evaluates the caller's token against the object's DACL and stamps a granted-access mask into the handle-table entry; later reads and writes are validated against that mask, not re-evaluated against the security descriptor. To actually cut access you have to terminate the holder or otherwise force the handle closed.
  • How does a Windows handle end up as a C runtime file descriptor in a ported program?
    The C runtime keeps its own descriptor table layered on top of handles. `_open_osfhandle` wraps an existing `HANDLE` in a CRT descriptor, and `_get_osfhandle` recovers the underlying handle from one. So the integer a ported POSIX-style program passes around is a CRT artefact; the kernel only ever knows about the handle.

saying these in an interview costs you the question

  • Says a HANDLE is a pointer you can dereference
  • Assumes a handle value is valid in another process
  • Thinks CloseHandle always destroys the object
  • Believes handles are inherited automatically like Unix descriptors
  • Checks only for NULL after CreateFileW

context