Why are kernel HANDLEs always a multiple of four?

Date:January 21, 2005 / year-entry #20
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20050121-00/?p=36633
Comments:    27
Summary:Not very well known is that the bottom two bits of kernel HANDLEs are always zero; in other words, their numeric value is always a multiple of 4. Note that this applies only to kernel HANDLEs; it does not apply to pseudo-handles or to any other type of handle (USER handles, GDI handles, multimedia handles...)...

Not very well known is that the bottom two bits of kernel HANDLEs are always zero; in other words, their numeric value is always a multiple of 4. Note that this applies only to kernel HANDLEs; it does not apply to pseudo-handles or to any other type of handle (USER handles, GDI handles, multimedia handles...) Kernel handles are things you can pass to the CloseHandle function.

The availability of the bottom two bits is buried in the ntdef.h header file:

// Low order two bits of a handle are ignored by the system and available
// for use by application code as tag bits.  The remaining bits are opaque
// and used to store a serial number and table index.

#define OBJ_HANDLE_TAGBITS  0x00000003L

That at least the bottom bit of kernel HANDLEs is always zero is implied by the GetQueuedCompletionStatus function, which indicates that you can set the bottom bit of the event handle to suppress completion port notification. In order for this to work, the bottom bit must normally be zero.

This information is not useful for most application writers, which should continue to treat HANDLEs as opaque values. The people who would be interested in tag bits are those who are implementing low-level class libraries or are wrapping kernel objects inside a larger framework.

Comments (27)
  1. Aaargh! says:

    Wow, that’s ugly.

    Why not add an extra parameter "bool suppresCompletionNotification" to the functions that use this mechanism ?

  2. Because it would have to be added to every API that could possibly cause an event to be set to the signalled state.

    This includes the ReadFile and WriteFile API, as well as all the synchronization APIs.

    That means that literally dozens of APIs would have had to be updated.

    Remember: completion ports were added in NT 3.5, it would have been difficult (to say the least) to rev all the Win32 APIs to handle this.

    Instead, they encoded the "suppress notifications" functionality into the handle itself.

  3. Brian says:

    I like how it says, "For compatibility with future operating systems, it is best to call GetCurrentProcess instead of hard-coding this constant" but you know that in 10 years, the App. Compat. team is going to have to figure out a way to make at least 3 dozen applications work that all used the constant value.

  4. Mathias says:

    The ability to avoid a completion in a completion port for a particular operation is useful for perf. Consider:

    A) I post an operation (read, write, what have you)

    B) The file handle is already attached to a completion port

    C) I know it’s going to take a while. I don’t want to make this call synchronous since I can reuse the thread for other things in the mean time.

    D) I don’t want another thread to preempt this thread when this operation completes. For some other operations on this file handle, it’s ok but for this particular one, it isn’t.

    E) In other words, I wan’t to go verify on my own terms with my current thread whether the operation has completed. I’ll probably do that when I have some spare cycles.

    Thus it’s useful to be able to disable completion for a particular operation.

    Someone correct me if I screwed up…

  5. MYG says:

    Why not just make the completion notification another attribute of the underlaying kernel object? Then it could have been settable with:

    SetCompletionNotification(handle, TRUE|FALSE);

    This just kind of smells as a hacked on interface. I wonder what Cutler thought of this hack?

  6. You didn’t actually answer the question in the title: Why are the low two bits always zero? Because it’s really a pointer to a pointer, which is always aligned on a four-byte boundry. (Which is actually readable from user-space, but would I emphatically not suggest doing so on any code that you’re planning on shipping or want to be stable across any sort of OS upgrade, or you’ll give Raymond more backwards-compatability nightmares. Read: debugging tools and the fun of it only.)

  7. It’s true that this information is not very interesting for people writing applications, but knowing what kernel handles look like is very useful for debugging.

    For example, if you see a printf call stuck in WriteFile with a handle like 0x5fc you know that your stdout has probably been redirected. Regular standard handles look totally different (on my system stdin/out/err are 3, 7 and 11 respectively).

  8. Raymond Chen says:

    (Um, James, try dereferencing 0x5fc and see what that gets you… Kernel HANDLEs are not pointers to anything.)

  9. Waleri says:

    Larry, well, instead of adding bool parameter to a bunch of functions, they could simply add a EnableNotifications(HANDLE,BOOL) API instead…

  10. Raymond Chen says:

    See my previous explanation of why EnableNotifications wouldn’t work: http://weblogs.asp.net/oldnewthing/archive/2005/01/21/358109.aspx#358334

  11. What did Cutler think of this hack?

    I mean, given that kernel handles are always multiples of four, I imagine that he was one of the people that thought this up. Unless ntdef.h has changed much since NT3.51?

  12. Raymond Chen says:

    Again, that affects all I/O on that handle, not just the one I/O call you’re making – see "cola"s example above. If you did a MakeHandleNotifyable(hfile, FALSE), then you just screwed up your other threads which are doing async I/O on that handle simultaneously and expecting completion notifications.

    I think people fail to realize that the completion suppression flag is a >per-I/O-request< flag, not per-file or per-handle.

  13. cola says:

    Because someone in the API team wanted to use a filehandle with non-APC asynchronous notification without spamming the completion port, and they knew a programmer in the OS team, and the OS manager didn’t want to change the interface just for one programmer, so they hacked it in and it got documented?

    (Also the binary and source have to be backwards compatible and C compatible, so: no changing the arguments to DeviceIoControl in the DLL; *or* with a #define in the source; *or* with C++ overloading either! There has to be an entirely new function name.)

  14. cola says:

    Let’s see if we can figure out why anyone would want to register a completion port and then to suppress completion port notifications.

    How about a synchronous write? Like:

    BOOL FakeSyncWrite(HANDLE hfile, int offset_, char *buf, int *length) {

    BOOL success = 0;

    waitobj = CreateEvent(name=NULL, security=NULL, manual=TRUE, signaled=FALSE);

    OVERLAPPED async = { offset: offset_, hEvent: waitobj };

    *(int*)&async->hEvent |= 1; // suppress completion ports, if any

    success = WriteFile(hfile, buf, length, &async)

    && WaitForSingleObject(waitobj, delay=INFINITE);

    if(CloseHandle(waitobj)) return success;

    else return 0;


  15. A says:

    I think Waleri was requesting a function like:

    HANDLE MakeHandleNotifieable(HANDLE x)


    return x | 1;


    so that he doesn’t have to hard-code 1 in his program.

  16. Raymond Chen says:

    But that would change the behavior for *all* I/O against the file, not just for the one I/O request. And if you wanted to disable completion notification for all I/O against a file, then don’t associate it with a completion port to begin with!

  17. Waleri says:

    Ops, sorry missed that one…

    Good point, but still, its better be via API call rather than set a bit directly.. something like

    HANDLE MakeHandleNotifieable(HANDLE);

    This would be much better than

    *(int*)&async->hEvent |= 1;

    Oh, well, from now on, our handles will be always round of four :)

  18. Anonymous says:

    Console handles have something different in the bottom

    2 bits as well since they aren’t kernel objects but

    get passed to ReadFile, WriteFile, etc APIs.

  19. Len says:

    When using the same ‘handle’ between threads I always thought you were supposed to use DuplicateHandle when giving it up to other threads? Or am I missing something here.

    I can see the use of the ‘spare cycle’ example above. But the function to set something inside the handle struct sounds much cleaner? Then clear on next io compleation? When using other threads? Or does DuplicateHandle ‘cheat’ and reuse some of the same stuff underneath?

    Course it is a BIT (heh) late now…

  20. Raymond Chen says:

    Again people, remember, there can be multiple outstanding overlapped I/O’s on a single handle. (Hence the whole "overlapped" thing.) You can’t set a flag on the handle struct because the same handle is being used for multiple I/O’s. After all, if you could have only one outstanding I/O on a handle at a time, there would be no need for the event in the first place! You could just wait on the file handle.

    Clearly there is widespread misunderstanding of how overlapped I/O works. Most of the "suggestions" on how to "fix" the completion port "problem" completely miss the point of overlapped I/O.

  21. Anonymous says:

    lazybones &raquo; Dirty trick

  22. Jon Potter says:

    Well it was just an example :)

  23. Jon Potter says:

    I think people are misunderstanding the original article, and thinking that the event handle itself needs to have its LSB set to prevent completion port notification.

    What Raymond (and the docs) are saying is that the hEvent _member_ of the OVERLAPPED structure must have the LSB set to prevent completion port notification, eg:


    o.hEvent = hEvent;

    *(DWORD*)&o.hEvent | = 1;

    Or something like this:

    #define EventNoNotify(e) (HANDLE)(((DWORD)e)|1)


    o.hEvent = EventNoNotify(hEvent);

    You aren’t modifying the event handle, you’re just modifying the member of the OVERLAPPED struct when you pass the event handle to the function in question.

  24. Thomas says:

    @Jon Potter: note that you need to typecast handles to ULONG_PTR/DWORD_PTR, not DWORD. You want to write 64bit compatible code, don’t you?


    o.hEvent = hEvent;

    *(ULONG_PTR*)&o.hEvent | = 1;


    #define EventNoNotify(e) (HANDLE)(((ULONG_PTR)e)|1)

  25. Swamp Justice says:

    While coding, I sometime ask myself, jokingly: What Would Cutler Do? (WWCD?)

  26. cola says:

    Heh, my "interesting" code for modifying the bits of a void pointer seems to have caught on. I think it’s actually clearer than the same code with parentheses added, and the precedence happens to be correct. (Although I shouldn’t have used int.)

    Although ov->hEvent is the value you are modifying, what you are doing is setting a tag bit on the OVERLAPPED structure. It has no effect elsewhere, probably. So the macro should apply to the structure:

    #define SetCPortSuppression(overlapped) (*(LONG_PTR*)&(overlapped).hEvent))|=1

  27. Hi NTDebuggers, this week’s puzzler just so happens to match its number: 0x000000006 = ERROR_INVALID_HANDLE.

Comments are closed.

*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index