It is well known that a handle for any of the numerous objects of the windowing system, e.g., windows, hooks and cursors—let’s call them user objects and user handles—is not a formal HANDLE such as used for access to kernel-mode objects such as files, processes, threads (or even window stations and desktops). Though the windowing system’s Handle Manager is nowadays in kernel mode, it does not use the kernel’s Object Manager. That this separate design and implementation is well known may be mostly because it comes with advantages and disadvantages that programmers arguably do need to understand if they want their Windows programs to use the windowing system efficiently. Were it not well known for that, however, it would have become so because the openness of the windowing system’s Handle Manager has in its time caused much concern for computer security.

The openness is very much by design. User handles and objects are nowadays created by WIN32K.SYS in kernel mode. Before version 4.0, they were creations of WINSRV.DLL in the CSRSS.EXE process. But whether it is the kernel or CSRSS that acts as server to potentially numerous clients, much of the point to the windowing system is that its handles are directly transportable between client processes and its objects are simultaneously visible to the clients.

Each handle indexes a HANDLEENTRY structure in an array that acts as the handle table. The table is in shared memory, pointed to by the aheList member of the SHAREDINFO structure that each process gets an instance of when its USER32 connects to WIN32K. The current size of the table, as a count of entries, is held separately as the cHandleEntries member of the SERVERINFO structure that is also in shared memory.

Documentation Status

The HANDLEENTRY is not documented. Though symbol files for WIN32K.SYS in Windows 8 and higher name the HANDLEENTRY in the C++ decorations of internal routines, type information for the structure is present in symbol files for Windows 7 only—not before and not since.

That said, these names (for both the structure and its members) that are known with certainty from symbol-file type information in version 6.1 are just those that were long known in public as names that Microsoft likely does use in the source code. These show in the plain-text output of meta-commands that are implemented in debugger extensions as supplied by Microsoft with its debuggers in various kits for both kernel-mode and user-mode programming. See, for instance, USEREXTS.DLL from as far back as the Device Driver Kit (DDK) for Windows NT 3.51. Though the HANDLEENTRY structure is not formally documented, Microsoft certainly has understood that knowledge of it may help programmers in the depths of debugging what they’re doing with Windows!


If only in retail builds of Windows, the HANDLEENTRY is 0x0C or 0x18 bytes in 32-bit and 64-bit Windows, respectively, except that before its one known change—for version 3.51—it was only eight bytes.

Offset (x86) Offset (x64) Definition Versions Remarks
0x00 0x00
HEAD *phead;
0x04 0x08
PVOID pOwner;
3.51 and higher previously in HEAD
0x04 (3.10);
UCHAR bType;
0x05 (3.10);
UCHAR bFlags;
0x06 (3.10);

The phead is the kernel-mode address of the object that corresponds to the handle. All objects that can be referred to by a handle begin with either a HEAD or a compatible elaboration such as a THRDESKHEAD or PROCDESKHEAD (depending, in these examples, on whether ownership of the object is by thread or process). The object may be intended to have user-mode visibility through some user-mode address that can be computed from the kernel-mode address and other input, notably by subtracting a per-process delta such as may exist in the process’s instance of the SHAREDINFO.

Not shown in the formal definition is that when a HANDLEENTRY is free, the phead does not point to any type of object but instead holds the index of another free HANDLEENTRY.

What pOwner points to, if anything, is either a PROCESSINFO or a THREADINFO, depending on the nature of ownership for the type of object. Again, this is a kernel-mode address—but it is a leaked kernel-mode address in the sense that what it points to is not mapped to any user-mode address.

Numerical interpretation of the bType varies greatly in the early history and is subject to misinformation (apparently from lazy analysis of output from debugger extensions). An enumeration of known possibilities is attempted in a separate section, below.

Though bits have been added to the bFlags, none are known to have changed. Again, an enumeration of known possibilities is attempted in a separate section, below.

In general, the high 16 bits of a handle must match the wUniq member in the HANDLEENTRY that is selected by the handle’s low 16 bits. The wUniq is incremented each time a handle is freed. Should the freed handle somehow get presented again for use, it will be stale in the sense of not selecting any valid HANDLEENTRY—well, not until the corresponding HANDLEENTRY is allocated and freed 64K times. This safeguard provided by the wUniq member does not apply in the exception to the generality, which is when the high 16 bits of a handle are all clear or all set (which cases occur when the handle has come from 16-bit code).

Object Types

No formal enumeration of defined object types is known. The WINSRV executable in version 3.10 helpfully has descriptive strings pointed to from an array in increasing order of object type. No use is known to be made of the array, however, and it is here supposed that the strings and array were intended for debug output and are retained in the executable only by oversight when compiling for release. They indeed do not survive into the executable in version 3.51, but plainly are present in the source code since the .DBG file for that version has symbols for the strings and the array but with an OMAP entry that shows they were eliminated. Where these names do survive is the USEREXTS.DLL debugger extension, notably to support the !dhe command.

Of course, each user object is accessed in the code not by name but as a structure. Microsoft’s names for almost all these structures can nowadays be known with certainty because public symbol files for WIN32K.SYS in Windows 8 and higher provide the C++ decorations of the names of routines that work with the structures or of data that points to instances of the structures, e.g., for a list. For many of these structures, Microsoft’s names were known sooner with as much certainty by matching executable code with type information in the public symbol files for WIN32K.SYS from Windows 7 and with less certainty by matching against names and other details learnt from debugger extensions (that Microsoft seems to have last made public for Windows 2000).

The great change, and complication of description, was that a renumbering for version 3.51 shifted nearly half the defined object types towards the end. Ignore version 3.10, and the table looks much more orderly.

Type Object Name Versions Remarks
0x00 (3.10)   Callback 3.10 only  
0x01 (3.10);
none Free all  
0x02 (3.10)   Zombie 3.10 only next as 0x0F
0x03 (3.10) WINDOWSTATION WindowStation 3.10 only next as 0x0A
0x04 (3.10) DESKTOP Desktop 3.10 only next as 0x0B
0x05 (3.10);
WND Window all  
0x06 (3.10);
MENU Menu all  
0x07 (3.10) SVR_INSTANCE_INFO DDE access 3.10 only next as 0x0C
0x08 (3.10) DDECONV DDE conv 3.10 only next as 0x0D
0x09 (3.10);
CURSOR Icon/Cursor all  
0x0A (3.10) ACCELTABLE Accelerator 3.10 only next as 0x09
0x0B (3.10) HOOK Hook 3.10 only next as 0x05
0x0C (3.10);
SMWP WPI(SWP) structure all  
0x05 HOOK Hook 3.51 and higher  
0x0D (3.10) XSTATE DDE Transaction 3.10 only next as 0x0E
0x0E (3.10);
0x06 (3.51 to 4.0)
THREADINFO ThreadInfo 3.10 to 4.0  
0x0F (3.10);
0x07 (3.51)
Q Input Queue 3.10 to 3.51  
0x07 (4.0);
  Clipboard Data 4.0 and higher  
0x10 (3.10);
0x08 (3.51 to 4.0);
CALLPROCDATA CallProcData all  
0x09 (3.51 to 4.0);
ACCELTABLE Accelerator 3.51 and higher previously 0x0A
0x0A WINDOWSTATION WindowStation 3.51 only previously 0x03
0x0B DESKTOP Desktop 3.51 only previously 0x04
0x0C (3.51);
0x0A (4.0);
SVR_INSTANCE_INFO DDE access 3.51 and higher previously 0x07
0x0D (3.51);
0x0B (4.0);
DDECONV DDE conv 3.51 and higher previously 0x08
0x0E (3.51);
0x0C (4.0);
XSTATE DDE Transaction 3.51 and higher previously 0x0D
0x0F (3.51);
0x0D (4.0);
  Zombie 3.51 to 4.0 previously 0x02
MONITOR Monitor 5.0 and higher  
0x0E (4.0);
KL Keyboard Layout 4.0 and higher  
0x0F (4.0);
KBDFILE Keyboard File 4.0 and higher  
0x0F EVENTHOOK WinEvent Hook 5.0 and higher  
0x10 TIMER Timer 5.0 and higher   
0x11 IMC Input Context 5.0 and higher  
0x12 HIDDATA   5.1 and higher  
0x13 DEVICEINFO   5.1 and higher  
0x14 TOUCHINPUTINFO   6.1 and higher  
0x15 GESTUREINFO   6.1 and higher  
0x16 HID_POINTER_DEVICE_INFO   6.2 and higher  

Some types of user objects were discontinued by the windowing system’s migration to kernel mode in version 4.0. The WINDOWSTATION and DESKTOP became kernel objects in the sense of having handles that are subject to the Object Manager’s notions of security. The THREADINFO and Q became kernel-mode structures that are exposed through no sort of handle.

Version 4.0 is here thought to have retained an object type for the THREADINFO despite making the latter into a kernel-mode creation with no user-mode access. Be aware that support for this conjecture just from what’s in the executable is slim: an array of pool allocation tags in increasing order of object type has “Usti” in just the right place for the supposed retention.

In no version is any use known of what version 3.10 names as Zombie. That it was renumbered as 0x0F for version 3.51 is certain. That it survives to version 4.0 is based on nothing more than having no idea what else might have been defined in its place only to be left unused in version 4.0 for someone to reassign, out of sequence, for version 5.0.

No name is known for the structure that supports the Clipboard Data. The structure is apparently too simple to survive inlining and get its name into symbol files. The object is used for passing arbitrary data, especially but not only for the clipboard. The structure is a HEAD and then a dword that holds the size in bytes of the opaque data that follows.

No name is yet found in any symbol files, neither in type information nor C++ decorations, for the structure that supports the Accelerator object. The name ACCELTABLE is instead known from tables of what might be termed structure offsets that debugger extensions from DDKs for early Windows versions use for their !dso command.

Not shown above for the object names that are learnt from the !dhe command is that USEREXTS version 4.0 has no name for the Keyboard File and version 5.0 cuts “WPI(SWP) structure” to “WPI(SWP) struct”.

Other Names

For completeness, it’s as well to note that other Microsoft sources of information about the HANDLEENTRY have other names for some object types.

Though the version 3.51 executable loses the names that show in debugger output, it has a different set of names which it puts to use for tagging the desktop heap. This tagging doesn’t survive the move to kernel mode in version 4.0—see that the RtlCreateTagHeap function, which is exported from NTDLL.DLL in version 3.51 and higher, never has been a kernel-mode export—but the names correspond to a separate set of names that does survive to later versions, just not in the executable. This other set shows in the output of the !du command and is notable because it continues for some object types that never have been valid to WINSRV or WIN32K and anyway has plainly incorrect names for some, even most, numerical values.

Type Name (Tag Heap) Name (!du) USEREXTS Versions Remarks
0x00 FREE Free 3.51 to 5.0  
0x01 WINDOW Window 3.51 to 5.0  
0x02 MENU Menu 3.51 to 5.0  
0x03 CURSOR Cursor 3.51 to 5.0  
0x04 SETWINDOWPOS SetWindowPos 3.51 to 5.0  
0x05 HOOK Hook 3.51 to 5.0  
0x06 THREADINFO Thread Info 3.51 to 5.0 incorrect in 5.0
0x07 INPUTQUEUE Input Queue 3.51  
  Clip Data 4.0 to 5.0 incorrect in 5.0
0x08 CALLPROC Call Proc 4.0 to 5.0 incorrect in 5.0
0x09 ACCELTABLE Accel Table 4.0 to 5.0 incorrect in 5.0
0x0A WINSTATION WindowStation 4.0 to 5.0 incorrect
0x0B DESKTOP DeskTop 4.0 to 5.0 incorrect
0x0C DDEACCESS DdeAccess 4.0 to 5.0 incorrect
0x0D DDECONV DdeConv 4.0 to 5.0 incorrect
0x0E DDEXACT DdeExact 4.0 to 5.0 incorrect
0x0F ZOMBIE Zombie 3.51 to 4.0 incorrect in 4.0
  Monitor 5.0 incorrect
0x10 CTYPES Ctypes 3.51 to 5.0  
0x11 CONSOLE Console 3.51 to 5.0  
0x12 GENERIC Generic 3.51 to 5.0  
0x13 HM      
0x14 LOCK      

The names for tagging the desktop heap exist in the executable as a null-terminated sequence of null-terminated Unicode strings. Except towards the end, these strings do correspond reliably to the object types, but no reason is known that any of them must correspond at all.


Debugger extensions from early development kits helpfully have descriptive strings for the bits within the bFlags. It seems highly plausible that these are the macros that are used for the bits in the source code. They are not, however, all the defined bits.

Mask Symbol Versions
0x20 HANDLEF_GRANTED 5.0 and higher
0x40   5.0 and higher

The 0x01 bit is set when the object is marked for destruction, which cannot happen while the cLockObj in the HEAD is non-zero. The 0x02 bit is set if destruction ever begins.

The 0x40 bit dates from version 5.0 but is not named by the USEREXTS from that version. It is set when the object is not on the desktop heap despite being a type that ordinarily would be. One known case is the window that is its terminal’s desktop owner.