What does the CS_CLASSDC class style do?


Last time, I talked about the historical background for the CS_OWNDC class style and why it starts out sounding like a good idea but when you think about it some more turns out to be an awful idea.

The CS_CLASSDC class style is the same thing, but worse, for it takes all the problems of CS_OWNDC and magnifies them. Recall that the CS_OWNDC class style instructs the window manager to create a DC for the window and use that single DC in response to calls to BeginPaint and GetDC. The CS_CLASSDC takes this one step further and creates a DC for all the windows of that class. So that problem I showed last time with a function that thought it had two different DCs for a window can now happen even across windows. You think you have one DC for one window and another DC for another window, but in fact they are the same!

What makes this even worse is that two threads can both be using the same DC at the same time. There is nothing in GDI to forbid it; it's simply a race to see which thread's changes prevail: "Last writer wins." Imagine two threads that happen each to have a CS_CLASSDC window from the same window class, and suppose both windows need to be repainted. Each window gets a WM_PAINT message, and the two threads both go into their painting code. But what these threads don't know is that they are operating on the same DC.

Thread A Thread B
HDC hdc = BeginPaint(hwnd, &ps);
HDC hdc = BeginPaint(hwnd, &ps);
SetTextColor(hdc, red);
SetTextColor(hdc, blue);
DrawText(hdc, ...);
DrawText(hdc, ...);

The code running in Thread A fully expected the text to be in red since it set the text color to red and then drew text. How was it to know that just at that moment, Thread B went and changed it to blue?

This is the sort of race condition bug that you'll probably never be able to study under controlled conditions. You'll just get bug reports from customers saying that maybe once a month, an item comes out the wrong color, and maybe you'll see it yourself once in a while, but it will never happen when you have debugger breakpoints set. Even if you add additional diagnostic code, all you'll see is this:

...
SetTextColor(hdc, red);
ASSERT(GetTextColor(hdc) == red); // assertion fires!
DrawText(hdc, ...);

Great, the assertion fired. The color you just set isn't there. Now what are you going to do? Maybe you'll just say "Stupid buggy Windows" and change your code to

// Stupid buggy Windows. For some reason,
// about once a month, the SetTextColor doesn't
// work and we have to call it twice.
do {
 SetTextColor(hdc, red);
} while (GetTextColor(hdc) != red); 
DrawText(hdc, ...);

And even that doesn't fix the problem, because Thread B might have changed the color to blue after the GetTextColor and the call to DrawText. Now, it's only once every six months that the item comes out the wrong color.

You swear at Microsoft and vow to develop Mac software from now on.

Okay, so now I hope I've convinced you that CS_CLASSDC is a horrifically bad idea. But if it's so fundamentally flawed, why does it exist in the first place?

Because 16-bit Windows is co-operatively multi-tasked. In the 16-bit world, you don't have to worry about another thread sneaking in and messing with your DC because, as I already noted, the fact that you were running meant that nobody else was running. This whole multi-threaded disaster scenario could not occur, so CS_CLASSDC is only slightly wackier than CS_OWNDC. The introduction of pre-emptive multi-tasking with multiple threads in a single process is what took us into the world of "this has no chance of ever working properly". The class style exists so people who used it in 16-bit code can port to Win32 (as long as they promise to remain a single-threaded application), but no modern software should use it.

Comments (26)
  1. Derek Park says:

    What I don’t understand is, why doesn’t this page make it painfully clear that CS_OWNDC and CS_CLASSDC are bad news?

    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windowclasses/aboutwindow.asp

    It refers to CS_OWNDC as /convenient/, and makes it sound as if the only drawback is the use of the GDI heap (and then only on 95/98/ME).  The comment for CS_CLASSDC makes it sound like the multi-thread failure will be almost graceful.

    While I (kind of) understand leaving these flags in for backwards-compatibility, the documentation itself can, and should, be updated to accurately reflect the fact that these flags are not really not safe.  A simple "do not use these" would be a great addition (maybe with a link to these posts . . . ).

  2. Arlie Davis says:

    Wow, that’s exactly what I was going to say.  I know about this, you know about this, but the people who don’t know are the ones who are still reading MSDN and writing new apps, or maintaining old ones.

    They need to be told!  Why don’t the docs say, in big red letters, "Don’t use!  This is for apps written 20 years ago!  There’s not even any benefit to using these!"?

  3. Miles Archer says:

    I think this is Raymond’s point exactly. He can’t change the docs so he posts the warning here.

  4. Chandler says:

    Since this seems to be left in solely for backwards compatability, why don’t they just depreciate it and post a comment on MSDN to explain it????  They do that for the "non-secure" sprintf’s, et al. You have to explicitly state that "yes, I want to do that" by adding a special #define to surpress the compile warnings.

    Small amount of work, doesn’t break anything outright and bring about a more enilightened user (ok, that’s a bit hopeful on the last part…)

    — Justin

  5. Dave says:

    Wouldn’t examples like this make a good case for implementing backwords compatability in the form of virtualization/sandboxes? This would let you gracefully deprecate functions/flags like CS_OWNDC in the OS while letting the Win16 sandbox handle it normally (in theory anyways).

    [Since the behavior is opt-in, there’s nothing to do. Or are you saying “Disable CS_OWNDC but if people find an app that relies on it, they can apply the compatibility shim to it”? Why make people manually do what the system already does automatically: If the app passes CS_OWNDC then it gets the CS_OWNDC behavior. The flag still works and behaves as documented. -Raymond]
  6. Dan McCarty says:

    Presumably, if you’re explicity using the CS_CLASSDC style you should know what the heck you’re doing, but one wonders at what point elements like these should just be deprecated.  Larry Osterman touched on deprecating API’s a while back: http://blogs.msdn.com/larryosterman/archive/2005/05/11/416497.aspx)

  7. AlanM says:

    Look, overall, I agree that CS_OWNDC and CS_CLASSDC are generally obsolete, but what if you have a class of window that you want, by design, to have a static set of properties across all instances/threads/etc.? In other words, the pen, brushes, font, colors, everything, would always be the same throughout the life of the window.

    For example, let’s say I want a window to draw a progress bar in a single color (and for various reasons, I want to actually create a window class rather than just go windowless and draw into a DC). My design says there will always be exactly one background color for all instances, and one foreground color for the progress. No text will be drawn, or, if it does, will use the same standard system font everytime, in the same color as the foreground pen. I would think a CS_CLASSDC class style would be just the ticket for this.

    What am I missing?

  8. Craig Ringer says:

    Is this the sort of thing that the Miscellaneous/DangerousAPIs check in Application Verifier checks for? I certainly hope so … since the chances of this sort of bug being detected in old code aren’t high otherwise.

  9. I can see an immediate benefit to CS_CLASSDC: using an appropriate synchronisation mechanism (e.g. a semaphore), you can have any number of applications write their status to the same window.

    However, it would be less complex and buggy to use a separate thread that does nothing but accept status updates and log them to the window.

    If I were implementing this feature today, sure, I *could* do it the same way I might have done it in an old version of Windows… but why? There are better ways to do it.

    I think the modern issue is not so much that there’s no good use for this feature, it’s that almost anything which might make good use of this feature can accomplish the same thing in a much better way WITHOUT it.

    However, there is a saying in the UNIX world that UNIX does not stop you from doing stupid things because that would stop you from doing clever things. I think the same is true here. Someone out there may be truly brilliant and use CS_OWNDC or CS_CLASSDC to do something that would otherwise be impossible.

    The question is whether you consider it so important to prevent stupid, you would trade away the potential for clever. I tend to think the answer to that question is always "no".

  10. Daniel Garlans says:

    Just out of curiousity, is there a tool which will execute a win32 app, and intercepts/watches all of its calls and interactions with the operating system, and looks out for the use of such potentially bug-ridden options?

    of course if it’s your own code, you can simply find-in-files for certain keywords, but, what about if it’s someone elses app, or you want another external way to test your code.

    there has to be a way! :)

  11. Cheong says:

    Agreed it shouldn’t be marked depreciated, but warning should be made "more noticable" in the MSDN documentation about the undesired effects.

  12. cypherpunks says:

    I doubt Chen can change the docs, I’d imagine the paperwork to do so would be daunting.

    Anything with a large corporation takes a lot of effort, hence little change, as it is easier.

  13. nksingh says:

    Since these flags are being used for back-compat, can they be removed from the headers and marked as reserved in the docs?  (Maybe we run into the ‘Microsoft uses undocumented APIs’ issue this way)

  14. Lex says:

    I prefer that it is mentioned here than nowhere but it does seem unfortunate that things like this aren’t marked as deprecated. We’re all guilty of rooting around and finding something just to get something that works every now and then.

    Again tho’ I’m grateful that at least the recommendation is available somewhere (not to mention that a deprecated flag generally doesn’t give such a throurgh detailing of the reasons for its retirement).

  15. teflon dan says:

    while on the topic of DCs, I have always wondered if its really 100% necessary to deselect things out of DCs before the releasing, or destroying the DC.

    HDC hDC;

    HBITMAP orgBmp;

    hDC = GetDC(hWnd);

    orgBmp = SelectObject(hDC, newBmp);

    // do something useful

    SelectObject(hDC, orgBmp);

    ReleaseDC(hWnd, hDC);

    return;

    Any thoughts?

  16. JST says:

    Yeah!  I wanna know too!

    What badness results from releasing a “dirty” DC?

    [Why does it matter? Just don’t do it. If I told you what could go wrong, you might say “Eh, that’s not so bad, I’ll just keep doing it.” -Raymond]
  17. A says:

    I doubt Chen can change the docs, I’d imagine the paperwork to do so would be daunting.

    Actually, it’s just a matter of firing off an e-mail to the SDK feedback address. You don’t even have to work for Microsoft. I’ve done this on several occasions, and each time my suggestions were incorporated in the next release.

  18. JST says:

    Ok.  I guess I deserved that.  And I should have known what your answer would be, Raymond.  Fine.  I’ll shut up now… except…

    What I was really asking about was not "What’s proper practice for a workaday Windows application programmer?", but "I’m curious about the internals of Windows — please spill the beanz."

    And I have no right to expect an answer to that.

  19. teflon dan says:

    re: switching back in the original objects…

    The reason I was wondering is, if you look back to examples dating all the way back to the win3.1 days, it was not done. even scads of MS examples in MSDN did not do it. And sometimes code can get quite complex and verbose trying to swap back in the original objects. It would be interesting to know how much time a SelectObject() takes, and just what can go wrong if you dont do it on the way out. As a matter of fact, I have not done it from the Win3.1 days until only in the last few years because BoundsChecker whines so loudly if you dont do it. And, I cant see that anything has evert gone wrong from not doing it. But, it would be nice to know, what could go wrong? Is this another of those cases like passing a GetDesktopWindow() into DialogBox() – where the OS is going to fix it anyway. Raymond, help us, you are our only hope.

  20. steveg says:

    This is the sort of thing where it might be worth breaking existing source code (collective gasps sound), by updating win.*.h to exclude deprecated items unless #define DEPRECATED_WIN0310 is included somewhere.

    This would only be source-level deprecation, not binary support — MS is probably cursed with that for the next few versions of windows yet.

    Raymond, while I’m here, is it true the Win3.1 WinProc source code had a "goto OhMyGodICantBelieveImDoingThis"?

  21. xm says:

    2 teflon dan & JST:

    The MSDN doc for SelectObject() says it clearly (but without an explanation):
    … An application should always replace a new object with the original, default object after it has finished drawing with the new object…

    How about to ensure ‘unbinding’ of any possible graphics objects from the device context memory, before you release/destroy that DC. I mean there could be a problem (GDI leak) with releasing/destroying of some previously allocated and selected-in bitmap resources/classes.

    But I am curious too about that, are there any other ‘crucial’ reasons to do so?

    [You already know why; you just don’t realize it yet. -Raymond]
  22. A says:

    I never restore the original stock pens/brushes/bitmaps/etc. before releasing DCs, and I’ve never observed any GDI resource leaks or incorrect painting on either the 9x or NT platforms.

    So I, too, would like to see an explanation of what "could" go wrong if you fail to do this.

    (Note: I don’t use CS_CLASSDC or CS_OWNDC.)

    "The documentation says so" isn’t a good enough reason to go back and update/retest heaps of code.

  23. Tom says:

    Actually, it might be a good enough reason, because you don’t know what Windows is doing inside. So if the docs tell you to do something, you should probably be doing it, because you don’t have the required intimate knowledge of when, exactly, it’s safe not to.

    To make it easy, see SaveDC and RestoreDC.

    (This attitude has saved me loads of grief, loads of times ;)

  24. Norman Diamond says:

    Monday, June 05, 2006 2:48 PM by A

    > "The documentation says so" isn’t a good

    > enough reason to go back and update/retest

    > heaps of code.

    Sometimes it is.  The problem is that the answer still isn’t clear.

    Monday, June 05, 2006 6:56 PM by Tom

    > Actually, it might be a good enough reason,

    > because you don’t know what Windows is doing

    > inside. So if the docs tell you to do

    > something, you should probably be doing it,

    > because you don’t have the required intimate

    > knowledge of when, exactly, it’s safe not to.

    Yup.  Probably you should obey MSDN, because roughly somewhere around 75% of MSDN is accurate.  But we don’t know which 75% until we experiment.  Even after experimenting we don’t always know what to do, because we don’t know what Windows is doing inside.  About all I can do, usually, is that if the result of an experiment agrees with MSDN then I’ll usually rely on it.

    There are exceptions though.  For example for a long time MSDN has disagreed with MSDN about whether the first argument to a DllMain function has type HANDLE or type HINSTANCE.  A few months ago Microsoft posted a decision that the argument has type HINSTANCE.  Shortly after that, Microsoft posted a decision that Visual Studio will continue giving that argument type HANDLE.  Microsoft said that you can change the declaration, well sure you can change it, but if you change it to type HINSTANCE then Visual Studio gives you compile time errors.  This has been resolved as "won’t fix".  So whether or not we know what Windows is doing internally, and regardless of MSDN maybe undergoing edits to come into agreement with it, we can’t go back and update/retest code.

  25. Mr. Ed says:

    Why don’t the libraries check for single-threadedness if CS_OWNDC or CS_CLASSDC are specified?

    [(1) Which libraries? Do you mean the window manager? Or do you mean MFC/ATL/etc.? (2) How does one “check for single-threadedness”? -Raymond]
  26. Yuhong Bao says:

    For CS_CLASSDC and CS_OWNDC, how about when BeginPaint is called, lock the DC so another thread has to wait until the DC is released before drawing in it.

Comments are closed.

Skip to main content