Thread affinity of user interface objects, part 1: Window handles


Different objects have different thread affinity rules, but the underlying principles come from 16-bit Windows.

The most important user interface element is of course the window. Window objects have thread affinity. The thread that creates a window is the one with which the window has an inseparable relationship. Informally, one says that the thread "owns" the window. Messages are dispatched to a window procedure only on the thread that owns it, and generally speaking, modifications to a window should be made only from the thread that owns it. Although the window manager permits any thread to access such things as window properties, styles, and other attributes such as the window procedure, and such accesses are thread safe from the window manager's point of view, load-modify-write sequences should typically be restricted to the owner thread. Otherwise you run into race conditions such as the following:

wpOld = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)newWndProc);

LRESULT CALLBACK newWndProc(...)
{
 ... CallWindowProc(wpOld, ...); ...
}

If modifications to the window procedure are made carelessly from any thread, then between the first two lines, a second thread may change the window procedure of the window, resulting in newWndProc passing the wrong "previous" window procedure to CallWindowProc.

Why, then, does Windows even allow a non-owner thread from changing the window procedure in the first place? Because, as we all know, 16-bit Windows was a co-operatively multi-tasked system, which means that one thread could do anything it wanted secure in the knowledge that no other thread would interrupt it until it explicitly relinquished control of the CPU. Therefore, the above code sequence was safe in 16-bit Windows. And for compatibility reasons, the code continues to be legal, even though it isn't safe any more. (Note, however, that in an attempt to limit the scope of the damage, the window manager allows only threads in the process that owns the window to change the window procedure. This is a reasonable limitation since separate address spaces mean that function addresses in other processes are meaningless in the process that owns the window anyway.)

Next time, a look at device contexts.

Comments (7)
  1. Jack Mathews says:

    This is a great counter argument to your points about punching holes in virtual machines, and this is exactly the "screen door" I was talking about.

  2. Could you be more specific what "this" is? Are you saying that one of the holes punched in the virtual machine would be unifying the window lists and supporting subclassing across VMs?

  3. Jack Mathews says:

    "This" means the safety problems introduced in the Window Manager.

    I suppose, thinking it through, this is more the problem of Win32 being pretty close to code compatible with Win16 and less about VM’s. Even a simple function, like getting the size of text in a window caption and then getting that text, can cause you to get incorrect results depending on thread scheduling. These are fixable problems.

    I guess my real issue is still the thought that Win16 and Win32 should exist as two happy families living side by side code-wise. But I’ve beaten that to death already.

    (The problem I mention above is calling GetWindowTextLength(), then a thread makes the caption longer, then calling GetWindowText passing the text length from GetWindowTextLength will result in a truncated version of that longer string)

  4. Sean Bryant says:

    Does this also apply to the new subclassing functions? SetWindowSubclass, GetWindowSubclass?

  5. sandman says:

    <quote>

    Therefore, the above code sequence was safe in 16-bit Windows. And for compatibility reasons, the code continues to be legal, even though it isn’t safe any more.

    </quote>

    My, that is a strange definition of compatible.

    So, a program which was safe can now exihibt undefined behaviour as opposed to ellicting an error message!

    Surely window should invoke some sort of modal warning then in this case (non-owner thread modifing a window) .

    You could have a bunch of compaitblity flags removing this on an application basis, or if certain locks were held.

  6. No, an old program that was safe is still safe. But a new program is not safe. Think about it.

  7. Multi-threaded apartments do not pump messages.

Comments are closed.

Skip to main content