The GetCurrentThread function is like a check payable to Bearer: What it means depends on who’s holding it


The Get­Current­Thread function returns a pseudo-handle to the current thread. The documentation goes into significant detail on what this means, but I fear that it may have fallen into the trap of providing so much documentation that people decide to skip it.

Okay, so first of all, what is a pseudo-handle? a pseudo-handle is a sentinel value for HANDLE that is not really a handle, but it can act like one.

The pseudo-handle returned by Get­Current­Thread means, "The thread that this code is running on." Note that this is a context-sensitive proposition. All the text in MSDN is explaining the consequences of that sentence.

In a sense, the Get­Current­Thread function is like a GPS: It tells you where you are, not where you were.

Imagine somebody who had never seen a GPS before. When they park their car in a parking garage, they look at the parking level number printed on the wall and write it down. Then when somebody asks, "Where did you park your car?" they can look in their notebook and say, "Oh, it's on level 3." Some parking garages even have little pads of paper with the parking level pre-printed on it. When you park your car, you tear a piece of paper off the pad and tuck it into your purse or wallet. Then when you want to return to your car, you see the slip of paper, and it tells you where you parked.

Now suppose you hand that person a GPS device, and tell them, "This will tell you your current location."

"Great! My current location is level 3 of the parking garage."

They go out shopping or whatever they were planning on doing, and now it's time to go home. They can't remember where they parked, so they look at the device, and it says, "Your current location is 512 Main Street."

"Hey, you told me this told me where I parked my car."

"No, I said that it told your current location."

"That's right. And at the time you gave it to me, my current location was level 3 of the parking garage. So I expect it to say 'Level 3 of the parking garage'."

"No, I mean it tells you your current location at the time you look at it."

"Well, that's stupid. I can do that too." (Scribble.) "There, it's a piece of paper that says 'You are right here'."

The value returned by the Get­Current­Thread function is like the GPS, or the slip of paper that says "You are right here." When you hand that value to the kernel, it substitutes the current thread at the time you use it. It's like a check payable to Bearer: The money goes to whoever happens to take it to the bank.

This means, for example, that if you call Get­Current­Thread from one thread and pass the result to another thread, then when that other thread uses the value, the kernel will interpret to mean the thread that is using it, not the thread that called Get­Current­Thread.

It also means that the value cannot be meaningfully cached to remember the thread that created the value, because the point of Get­Current­Thread is to adapt to whoever happens to be using it.

The theory behind Get­Current­Thread and its friend Get­Current­Process is that it gives you a convenient way to refer to the current thread from the current thread or the current process from the current process. For example, you might pass the return value of Get­Current­Process to Duplicate­Handle in order to duplicate a handle into or out of the current process. Or you might pass the return value of Get­Current­Thread to Set­Thread­Priority to change the priority of the current thread. The "current thread" pseudo-handle let you simplify this:

BOOL SetCurrentThreadPriority(int nPriority)
{
 BOOL fSuccess = FALSE;
 HANDLE hCurrentThread = OpenThread(THREAD_SET_INFORMATION,
                            FALSE, GetCurrentThreadId());
 if (hCurrentThread)
 {
  fSuccess = SetThreadPriority(hCurrentThread, nPriority);
  CloseHandle(hCurrentThread);
}

to

BOOL SetCurrentThreadPriority(int nPriority)
{
 return SetThreadPriority(GetCurrentThread(), nPriority);
}

If you want to convert a pseudo-handle to a real handle, you can use the Duplicate­Handle function.

BOOL ConvertToRealHandle(HANDLE h,
                         BOOL bInheritHandle,
                         HANDLE *phConverted)
{
 return DuplicateHandle(GetCurrentProcess(), h,
                        GetCurrentProcess(), phConverted,
                        0, bInheritHandle, DUPLICATE_SAME_ACCESS);
}

BOOL GetCurrentThreadHandle(BOOL bInheritHandle, HANDLE *phThread)
{
 return ConvertToRealHandle(GetCurrentThread(), bInheritHandle, phThread);
}

BOOL GetCurrentProcessHandle(BOOL bInheritHandle, HANDLE *phProcess)
{
 return ConvertToRealHandle(GetCurrentProcess(), bInheritHandle, phProcess);
}

Armed with your knowledge of pseudo-handles, criticize the following code:

class CSingleThreadedObject
{
public:
 CSingleThreadedObject() : _hThreadCreated(GetCurrentThread()) { }

 bool OnCorrectThread() { return GetCurrentThread() == _hThreadCreated; }

private:
 HANDLE _hThreadCreated;
};

class CFoo : protected CSingleThreadedObject
{
public:
 CFoo() { ... }

 // Every method of CFoo checks whether it is being called on the
 // same thread that it was created from.

 bool Snap()
 {
  if (!OnCorrectThread()) return false; // multithreading not supported
  ...
 }

};

Follow-up exercise: Criticize the following code that attempts to address the issues you raised in the previous exercise.

// ignore error handling in this class for the purpose of the exercise
class CSingleThreadedObject
{
public:
 CSingleThreadedObject() {
  ConvertToRealHandle(GetCurrentThread(), FALSE, &_hThreadCreated)
}

 bool OnCorrectThread() {
  HANDLE hThreadCurrent;
  ConvertToRealHandle(GetCurrentThread(), FALSE, &hThreadCurrent);
  bool onCorrectThread = hThreadCurrent == _hThreadCreated;
  CloseHandle(hThreadCurrent);
  return onCorrectThread;
 }

private:
 HANDLE _hThreadCreated;
};
Comments (40)
  1. Joshua says:

    GetCurrentThreadId

    I suppose the primary benefit of GetCurrentThread and Process is they do not fail so you do not have to write error handling code.

  2. Damien says:

    I like the follow up exercise – why is it that whenever I ask the system to make a copy of a handle, it's never the same as the original copy I took some time ago?

  3. asdbsd says:

    Should have probably been a constant, then everything would be obvious.

  4. poizan42 says:

    Maybe the real question one should ask is why there are functions which returns constant values. If the pseudohandles was simply HANDLE_CURRENT_PROCESS and HANDLE_CURRENT_THREAD it would probably cause less confusion (like the HKEY_* pseudo handles).

  5. Paul Z says:

    I don't know that much about Windows programming, but I was successfully able to guess for the 2nd exercise that == isn't a valid way to check that two thread handles refer to the same thread (the first exercise, of course, is exactly the non-working code mentioned earlier). But then, what is the correct way?

  6. SimplyGed says:

    In the first example (CFoo) OnCorrectThread() will always return TRUE because it is comparing pseudo handles.

    In the second exercise it will always return FALSE because DuplicateHandle will produce a different handle each time and they will never match

  7. bozox says:

    @Paul Z: use GetCurrentThreadId() instead.

  8. Smithers says:

    asdbsd: That's exactly what I thought. Instead of

    HANDLE GetCurrentThread(void);

    why not simply

    #define CURRENT_THREAD ((HANDLE) -2)

    For the exercises:

    1. CSingleThreadedObject does not need to access the thread itself, just identify it. Hence it should use GetCurrentThreadId() instead of GetCurrentThread().

    2. Well, that completely failed to address the issue I raised, but never mind. The class needs a destructor which calls CloseHandle(_hCreatedThread);

    (Did I do a good job of missing the point there?)

    Actually, I see an issue with my first answer; after a thread terminates, another thread could start with the same ID.

    Assuming that Michael_99's comment on GetThreadId (msdn.microsoft.com/…/ms683233%28v=vs.85%29.aspx) is correct (a thread id remains unique as long as there is a handle to it, even after it has terminated), the solution should be

    bool OnCorrectThread() { return GetThreadId(_hThreadCreated) == GetCurrentThreadId(); }

    otherwise, I you would have to && that test with WaitForSingleObject(_hThreadCreated, 0) == WAIT_TIMEOUT.

  9. parkrrrr says:

    @Paul Z, @bozox

    Or, if you actually do have two thread handles you want to compare, use GetThreadId() on each of them and compare the results. That's the wrong tool for this exercise, because you never really needed handles at all, but it is an answer to the question you asked.

  10. Alex Cohn says:

    I am afraid the GPS metaphor is not very successful. I'd rather choose a person who looks out of the train window, sees the city of Leningrad, goes to sleep, and looks out of the same window again. To his astonishment, he sees Moscow.

    PS The idea comes from a Russian children poem by Samuil Marshak. The intrigue there was that the train was not connected to the engine, and it remained in Leningrad every time the passenger woke up.

  11. Henke37 says:

    Should have been a define instead, people are going to hardcode the value either way. Might as well let the compiler inline the value too.

  12. Antonio 'Grijan' says:

    @Paul Z: the correct way would be to store the thread ID returned by GetCurrentThreadID() and later compare to it. You can have several handles referring to the same object, but a thread's ID doesn't change. Obviously, if the thread terminates, its ID can be assigned to a new one, but that's another problem (and AFAIK, Windows tries not to reuse thread and process IDs, even if it is not in the contract).

  13. Silly says:

    Did you know that 'time' spelled backwards is 'emit'?

    Ancient Egyptian: I did not know that.

  14. Crescens2k says:

    @Smithers,Henke37:

    Afaik the value of the pseudo handle is implementation defined. So it is possible to change between Windows versions. This is why you prefer GetCurrentThread/GetCurrentProcess to hard coding the value. I know it may seem like it is prehistoric, but can you guarantee that the Windows 9x family returned the same values as the Windows NT based systems that we use these days do? Can you guarantee that Windows 7 returns the same values as Windows 8, or Windows 10? While it is unlikely that they will change values for the sake of changing them, there is no mention of the value or that it wont change in the documentation. Therefore you should not assume that the handle value will always be the same. All the documentation says is that it will return a pseudo-handle.

    Remember, taking advantage of undocumented behaviour is a future bug report waiting to happen. Surprisingly, if there is no mention of something in the documentation, it is undocumented.

  15. Mark says:

    Antonio: if the thread terminates while you've got a handle to it, surely the kernel object is still kept around, so the TID can't be reused?

  16. Ben says:

    I have a sneaky suspicion I wrote the code in today's puzzle…  

  17. Medinoc says:

    @Crescens2k: The point of their post is not "why not make it a constant now" but "why was it never a (documented) constant in the first place", which would have avoided all the problems you mention by making it contractual.

    The answer is probably "it's on the time machine to-do list".

  18. Antonio 'Grijan' says:

    @Mark: what you say makes a lot of sense. It depends on how the kernel tracks process/thread IDs (whether using the process/thread objects or a dedicated structure), and maybe it is implementation detail and not part of the contract. I would read documentation carefully before relying on it.

  19. Crescens2k says:

    @Medinoc:

    That answer wasn't a "why not make it constant now" answer anyway. While it may be worded in present tense, I did mention a possible heart of the issue all of the way since the early 90s. Cross version compatibility and future compatibility. It is easy to look backwards, but it is always much harder to look forwards.

  20. theultramage says:

    Can the pseudo-handle be meaningfully compared against real handles? In Microsoft Detours they have

       // Silently (and safely) drop any attempt to suspend our own thread.

       if (hThread == GetCurrentThread())

           return NO_ERROR;

    [Yup, that's a bug. -Raymond]
  21. SimonRev says:

    @theultramage

    That wouldn't be safe (although I would consider attempting to suspend the current thread worthy of a log event and exception rather than silent ignoring).

    However:

    if (GetThreadId(hThread) == GetCurrentThreadId()) is nearly as easy, and would be safe.  Possibly add a check that GetCurrentThreadId didn't return zero in the case of an invalid thread.

  22. SimonRev says:

    Hrm, I misread your comment ultramage.  If they do it in Detours, then it probably works, but I am not sure I would trust that unless they expect that hThread would have come from an earlier call to GetCurrentThread().

  23. Cherry says:

    I think they do it wrong in Detours. Yyou are right, this isn't safe (and therefore it doesn't work even though Microsoft themselves are doing it – it's nothing new that Microsoft's employees make mistakes too).

  24. skSdnW says:

    @Crescens2k: Win95 only uses the same value for the thread handle:

    kernel32!GetCurrentThread:

    bff93ed8 b8feffffff      mov     eax,0FFFFFFFEh

    bff93edd c3              ret

    kernel32!GetCurrentProcess:

    bff94633 b8ffffff7f      mov     eax,7FFFFFFFh

    bff94638 c3              ret

    Why -1 is not used I don't know, maybe it seemed too magical so it was reserved for future use?

  25. s says:

    @sxsdnw – -1 is already taken by INVALID_HANDLE_VALUE, which IIRC has that value for Win16 compat reasons.

  26. AKFrost says:

    reminds me of a Chinese idiom: 刻舟求劍.

  27. Silly Gisms says:

    My favorite parts of your blog is when you liken some programming detail to something real-worldish… GetCurrentThread is like a GPS device, very nice!

  28. alegr1 says:

    @Mauritis:

    The thread ID will stay valid while there is a handle opened to the thread. This keeps the thread object afloat, with the ID assigned to the object. The thread, in the meantime, may be long over.

  29. Antonio 'Grijan' says:

    @Maurits: if you have a group of threads created about the same time (all of them existed at a certain point in time), then your point is right: no two threads from the group will share the same ID. But if it is not the case, there is a corner case: thread A creates the object, thread A terminates, thread B is created and gets the same ID as thread A, thread B accesses the object. Then, even if thread B is not the original creator, it gets access to the object. If what alegr1 says in the previous comment is contractual (I haven't checked the documentation, sincerely), then the solution would be to keep a handle to the original thread inside the object.

  30. > if the thread terminates, its ID can be assigned to a new one

    The ID check tells you whether two threads are, in fact, the same.

    As long as both of the following are true, the ID check is safe:

    * There was at least one instant in time in which both threads were running.

    * At least one of the threads is running at the time of the ID check.

  31. Drak says:

    @Antonio: is that not why Maurits said:

    – There was at least one instant in time in which both threads were running.

    In your example thread A is terminated before thread B is created, so your example does not take that statement into account.

    Ps. does everyone else need to click 'sign in' twice before actually being signed in?

  32. Yuhong Bao says:

    @SimonRev: To be honest, GetThreadId is not available on WinXP (I wonder why GetProcessId is available on WinXP since SP1, but not GetThreadId).

  33. Mark says:

    theultramage: surely that's just ensuring that hThread *is* a real handle? If it's GetCurrentThread(), we should ignore it; if the user passes in a duplicated handle to the current thread, they deserve what they get.

    Maurits: your comment implies that thread IDs can be reassigned while the threads still exist. Does that mean my comment about holding a handle is incorrect, or is there just some simple intuition behind your assertion?

  34. Sven2 says:

    Thanks for this posting. I've checked the codebase of a project I'm working on, and it has the same bug as in your first example!

    Now I hope fixing this bug won't uncover all kinds of cross-thread-calls that weren't detected before.

  35. John M. Długosz says:

    I suppose that if it is determined that any major popular software assumes the constant used as the pseudo-handle, then MS won't change it.  That's the story I've seen on this blog before, e.g. the story behind MSVCRT.dll.

  36. Neil says:

    Maybe it would have been clearer if the pseudo-handles had been exported constants?

  37. Joshua says:

    @Neil: While exporting a constant or a variable is possible, it usually boils down to being a bad idea due to high penalties incurred from the way the loader has to process the fixups.

  38. Will says:

    I once actually bookmarked "Current Location" in the maps app on my phone when I was trying to remember where I parked.  I didn't know the area well, and I had just gotten a whizzy new smartphone.  It seemed like a foolproof plan.  I went to the restaurant, had a beer, had a nice walk on the beach.  Then, in the dark, I pulled out my phone, looked up the car bookmark and asked it for directions.  It said I was already there.  I became enlightened.  I also spent half the night wandering through a strange neighborhood I'd paid no attention to because my phone knew where I had parked.

  39. Yukkuri says:

    "I became enlightened."

    Sorry for laughing at your misfortune but this made me grin hard.

  40. Matt says:

    While I am not 100% sure I remember looking at the assembly produced by the mingw-w64 compiler and it inlined the function call to GetCurrentThread meaning any software compiled with it is effectively using a constant. That was several years ago with an early version of the mingw-w64 compiler so they may have fixed it since then.

Comments are closed.

Skip to main content