Why is the OwningThread member of CRITICAL_SECTION of type HANDLE, when it is denoting the thread ID?
Asked Answered
H

2

8

I'm trying to add some debug checking for a CRITICAL_SECTION unlocking code, and I tried the following:

...
  if (m_pCritSect) {
    ASSERT(m_pCritSect->OwningThread == GetCurrentThreadId());
    LeaveCriticalSection(m_pCritSect);
  }
}

From debugging CRITICAL_SECTIONS (With VS 2005, mostly on WindowsXP) I "know" that the value of OwningThread (member of the RTL_CRITICAL_SECTION structure defined in winnt.h) is the value of th ID of the thread holding the lock.

However thread IDs are represented by DWORD (typedef for unsigned long) values while this variable has type HANDLE (typedef for void*) requiring a reinterpret_cast for the use of the HandleToULong Macro from basetsd.h for the above code to work.

Even the MSDN docs state:

When the first thread calls the EnterCriticalSection routine, (...) OwningThread becomes the thread ID of the caller.

So why on earth is this defined as a HANDLE?


Edit Note: I found a statement where a poster suggests that the HANDLE / DWORD-Id mismatch is some known misfeature of some Windows internals. So maybe this is the case here too:

GetCurrentThreadId returns a DWORD, which I send up to the kernel in a message. PsLookupThreadByThreadId takes the thread Id in a HANDLE, ... ...

This is a known Windows API bug ("known" in that I talked to the relevant filter manager DEV about this, as it shows up in Filter Manager as well because of the I/O Manager API issue.) As long as you don't have more than a half billion or so threads and processes (they use a single shared handle table) you'll be fine. Maybe by the time that's a real issue, we'll be running something different. [RE: ThreadId to HANDLE for 64 bit?, 08 Aug 08 14:21, Tony Mason]

Headwaiter answered 1/10, 2012 at 14:31 Comment(7)
In any case reinterpret_cast is overkill. A static_cast would do. Both HANDLE and DWORD are integral types.Sherborn
@ArmenTsirunyan - NO, in VS2005 you cannot use a static_cast to cast a HANDLE to a DWORD: error C2440: 'static_cast' : cannot convert from 'HANDLE' to 'DWORD'Headwaiter
That's weird. Could you please tell me what they're typedefs for?Sherborn
@ArmenTsirunyan - HANDLE == void* / DWORD == unsigned longHeadwaiter
Wow, that's news for me. I've only seen HANDLE to be a typedef for some integral type... never a pointerSherborn
Last I checked HANDLE is defined as a boring struct pointer, i.e. typedef struct { int unused; } * HANDLE; This may have changed since last I perused WIN32 headers (who the hell does that for fun anymore).Universalism
It has not changed. HANDLE, HWND, just about all handle types are all pointers. Some languages, like Delphi, treat them as integral types, but they really are not.Cutlerr
R
8

Any identifier in the SDK whose name starts with RTL or Rtl is code or declarations that are part of the runtime layer, the glue that marries the well-documented Winapi to the undocumented native operating system api. The winapi is cast in stone, the native operating system changes heavily with each Windows release. Inevitably, the glue changes as well.

The winapi is the documented layer, the native operating system is undocumented. The runtime layer was undocumented as well, but over time parts of it were revealed. Either because it back-fills a missing feature in the winapi. Or, in this case, because is genuinely useful to troubleshoot problems. One core problem with doing so however is that once a declaration is revealed, Microsoft can never change it again. Because doing so will break existing programs, a great burden to its customers.

So surely, the ThreadOwner field once truly held the handle to the thread in a previous Windows version. Note how the LockSemaphore is misleading too, it is actually an auto-reset event. Too late to fix it, the cat is out of the bag.

Radiotherapy answered 1/10, 2012 at 16:19 Comment(0)
P
5

I believe that the main reason is that it is an implementation detail. I wouldn't be surprised if at one time in history it really was a handle or something like that.

Additionally, I would strongly suggest not using the internal members in production code, and I am not alone. If you look closely, synchronization APIs use CRITICAL_SECTION which you won't find documented as a structure in MSDN, and not RTL_CRITICAL_SECTION (which is typedef'ed to CRITICAL_SECTION)

The value which is stored in OwningThread member is taken from CLIENT_ID part of Thread Information Block. In CLIENT_ID it as modeled as PVOID which is likely why it is modeled in the same way within CRITICAL_SECTION:

typedef struct _CLIENT_ID
{
   PVOID UniqueProcess;
   PVOID UniqueThread;
} CLIENT_ID, *PCLIENT_ID;
Pledge answered 1/10, 2012 at 14:49 Comment(2)
I provided an MSDN link that walks through this structure (granted it's in the Advanced Debugging Tasks section) so I think it's quite OK to use this in debug checking code.Headwaiter
It will still not build if the structure changes, and I also don't consider ASSERT code real production code, as it is not deployed to the end user (i.e. in release build)Pledge

© 2022 - 2024 — McMap. All rights reserved.