What does the CS_OWNDC class style do?


Recall that window DCs are most commonly used only temporarily. If you need to draw into a window, you call BeginPaint or, if outside of a paint cycle, GetDC, although painting outside of a paint cycle is generally to be avoided. The window manager produces a DC for the window and returns it. You use the DC, then restore it to its original state and return it to the window manager with EndPaint (or ReleaseDC). Internally, the window manager keeps a small cache of DCs which it dips into when people come asking for a window DC, and when the DC is returned, it goes back into the cache. Since window DCs are used only temporarily, the number of outstanding DCs is typically not more than a handful, and a small cache is sufficient to satisfy DC demands in a normally-running system.

If you register a window class and include the CS_OWNDC flag in the class styles, then the window manager creates a DC for the window and puts it into the DC cache with a special tag that means "Do not purge this DC from the DC cache because it's the CS_OWNDC for this window." If you call BeginPaint or GetDC to get a DC for a CS_OWNDC window, then that DC will always be found and returned (since it was marked as "never purge"). The consequences of this are good, bad, and worse.

The good part is that since the DC has been created specially for the window and is never purged, you don't have to worry about "cleaning up the DC" before returning it to the cache. Whenever you call BeginPaint or GetDC for a CS_OWNDC window, you always get that special DC back. Indeed, that's the whole point of CS_OWNDC windows: You can create a CS_OWNDC window, get its DC, set it up the way you like it (selecting fonts, setting colors, etc.), and even if you release the DC and get it again later, you will get that same DC back and it will be just the way you left it.

The bad part is that you're taking something that was meant to be used only temporarily (a window DC) and using it permanently. Early versions of Windows had a very low limit for DCs (eight or so), so it was crucial that DCs be released as soon as they weren't needed. That limit has since been raised significantly, but the underlying principle remains: DCs should not be allocated carelessly. You may have noticed that the implementation of CS_OWNDC still uses the DC cache; it's just that those DCs get a special marking so the DC manager knows to treat them specially. This means that a large number of CS_OWNDC DCs end up "polluting" the DC cache, slowing down future calls to functions like BeginPaint and ReleaseDC that need to search through the DC cache.

(Why wasn't the DC manager optimized to handle the case of a large number of CS_OWNDC DCs? First, as I already noted, the original DC manager didn't have to worry about the case of a large number of DCs since the system simply couldn't even create that many in the first place. Second, even after the limit on the number of DCs was raised, there wasn't much point in rewriting the DC manager to optimize the handling of CS_OWNDC DCs since programmers were already told to use CS_OWNDC sparingly. This is one of the practicalities of software engineering: You can do only so much. Everything you decide to do comes at the expense of something else. It's hard to justify optimizing a scenario that programmers were told to avoid and which they in fact were already avoiding. You don't optimize for the case where somebody is abusing your system. It's like spending time designing a car's engine so it maintained good gas mileage when the car has no oil.)

The worse part is that most windowing framework libraries and nearly all sample code assume that your windows are not CS_OWNDC windows. Consider the following code that draws text in two fonts, using the first font to guide the placement of characters in the second. It looks perfectly fine, doesn't it?

void FunnyDraw(HWND hwnd, HFONT hf1, HFONT hf2)
{
 HDC hdc1 = GetDC(hwnd);
 HFONT hfPrev1 = SelectFont(hdc1, hf1);
 UINT taPrev1 = SetTextAlign(hdc1, TA_UPDATECP);
 MoveToEx(hdc1, 0, 0, NULL);
  
 HDC hdc2 = GetDC(hwnd);
 HFONT hfPrev2 = SelectFont(hdc2, hf2);
  
 for (LPTSTR psz = TEXT("Hello"); *psz; psz++) {
  POINT pt;
  GetCurrentPositionEx(hdc1, &pt);
  TextOut(hdc2, pt.x, pt.y + 30, psz, 1);
  TextOut(hdc1, 0, 0, psz, 1);
 }
  
 SelectFont(hdc1, hfPrev1);
 SelectFont(hdc2, hfPrev2);
  
 SetTextAlign(hdc1, taPrev1);
  
 ReleaseDC(hwnd, hdc1);
 ReleaseDC(hwnd, hdc2);
}

We get two DCs for the window. In the first we select our first font; in the second, we select the second. In the first DC, we also set the text alignment to TA_UPDATECP which means that the coordinates passed to the TextOut function will be ignored. Instead the text will be drawn starting at the "current position" and the "current position" will be updated to the end of the string, so that the next call to TextOut will resume where the previous one left off.

Once the two DCs are set up, we draw our string one character at a time. We query the first DC for the current position and draw the character in the second font at that same x-coordinate (but a bit lower), then we draw the character in the first font (which also advances the current position).

After the text drawing loop is done, we restore the states of the two DCs as part of the standard bookkeeping.

The intent of the function is to draw something like this, where the first font is bigger than the second.

H e l l o
H e l l o

And if the window is not CS_OWNDC that's what you get. You can try it out by calling it from our scratch program:

HFONT g_hfBig;

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
 LOGFONT lf;
 GetObject(GetStockFont(ANSI_VAR_FONT),
           sizeof(lf), &lf);
 lf.lfHeight *= 2;
 g_hfBig = CreateFontIndirect(&lf);
 return g_hfBig != NULL;
}

void
OnDestroy(HWND hwnd)
{
 if (g_hfBig) DeleteObject(g_hfBig);
 PostQuitMessage(0);
}

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
 FunnyDraw(hwnd, g_hfBig,
                 GetStockFont(ANSI_VAR_FONT));
}

But if the window is CS_OWNDC, then bad things happen. Try it yourself by changing the line wc.style = 0; to wc.style = CS_OWNDC; You get the following unexpected output:

HHeelllloo

Of course, if you understand how CS_OWNDC works, this is hardly unexpected at all. The key to understanding is remembering that when the window is CS_OWNDC then GetDC just returns the same DC back no matter how many times you call it. Now all you have to do is walk through the FunnyDraw function remembering that hdc1 and hdc2 are in fact the same thing.

void FunnyDraw(HWND hwnd, HFONT hf1, HFONT hf2)
{
 HDC hdc1 = GetDC(hwnd);
 HFONT hfPrev1 = SelectFont(hdc1, hf1);
 UINT taPrev1 = SetTextAlign(hdc1, TA_UPDATECP);
 MoveToEx(hdc1, 0, 0, NULL);

So far, execution of the function is pretty normal.

 HDC hdc2 = GetDC(hwnd);

Since the window is a CS_OWNDC window, the DC that is returned in hdc2 is the same one that was returned in hdc1. In other words, hdc1 == hdc2! Now things get exciting.

 HFONT hfPrev2 = SelectFont(hdc2, hf2);

Since hdc1 == hdc2, what this really does is deselect the font hf1 from the DC and select the font hf2 instead.

 for (LPTSTR psz = TEXT("Hello"); *psz; psz++) {
  POINT pt;
  GetCurrentPositionEx(hdc1, &pt);
  TextOut(hdc2, pt.x, pt.y + 30, psz, 1);
  TextOut(hdc1, 0, 0, psz, 1);
 }

Now this loop completely falls apart. At the first iteration, we retrieve the current position from the DC, which returns (0, 0) since we haven't moved it yet. We then draw the letter "H" at position (0, 30) into the second DC. But since the second DC is the same as the first one, what really happens is that we are calling TextOut into a DC that is in TA_UPDATECP mode. Thus, the coordinates are ignored, the letter "H" is displayed (in the second font), and the current position is updated to be after the "H". Finally, we draw the "H" into the first DC (which is the same as the second). We think we're drawing it with the first font, but in fact we're drawing with the second font. We think we're drawing at (0, 0), but in fact we're drawing at (x, 0), where x is the width of the letter "H", because the call to TextOut(hdc2, ...) updated the current position.

Thus, each time through the loop, the next character in the string is displayed twice, all in the second font.

But wait, the disaster isn't finished yet. Look at our cleanup code:

 SelectFont(hdc1, hfPrev1);

This restores the original font into the DC.

 SelectFont(hdc2, hfPrev2);

This re-selects the first font! We failed to restore the DC to its original state and ended up putting a "corrupted" DC into the cache.

That's why I described CS_OWNDC as "worse". It takes code that used to work and breaks it by violating assumptions that most people make (usually without realizing it) about DCs.

And you thought CS_OWNDC was bad. Next time I'll talk about the disaster that is known as CS_CLASSDC.

Comments (34)
  1. mastmaker says:

    Hey, I learnt something new today!

    You can actually allocate TWO DCs for the same window at the same time? I always found ONE DC to be sufficient for me, no matter how complicated the drawing. But then, I am no Picasso!

  2. whinger says:

    It’s hard to justify optimizing a scenario that programmers were
    told to avoid and which they in fact were already avoiding.

    Except for us suckers trying to write OpenGL apps, eh?

    [I don’t follow. I was referring to intentional violating the
    guidelines for how a particular feature should be used. Presumably,
    OpenGL has similar guidelines on how it should and shouldn’t be used.
    (Not being an OpenGL programmer I don’t know for sure. I’m thinking
    guidelines like “Don’t allocate unnecessary XYZ’s.”) -Raymond
    ]
  3. Carlos says:

    I wondered if using two DCs would be quicker so I measured it.  Using one DC (and repeatedly switching the font) the painting takes about 36% more time than using two DCs.  YMMV.

  4. Tim Dawson says:

    It hadn’t occurred to me either that I could use two DCs for drawing at the same time.

  5. Reinder says:

    “We failed to restore the DC to its original state and ended up putting a “corrupted” DC into the cache”

    Are
    you implying that doing this was, in old Windows versions, a way to do
    inter-process communication (in a really, really awkward way)?

    [I
    would hope that, nowadays, the OS would always give programs a clean DC
    whenever they  asked for one, or would at least maintain a
    separate cache per process]

    [The cache is per-thread so all you’re corrupting is yourself. -Raymond]
  6. KJK::Hyperion says:

    Little bit of trivia: the only part of Windows that uses CS_OWNDC exclusively are the console windows (go on, have a look with Spy++), altough I’m not exactly sure why (I suppose to allow drawing from outside the window’s thread? or maybe for performance?). It’s one of the many ways console windows are "special"

  7. Nick says:

    Another standard Windows app which makes use of the class is Windows Media Player 10. Curious, I checked and the old WMP 6.4 (mplayer2.exe) also uses the class.

    Would this be related to the video playback they do? I assume WMP 6.4 also uses DirectPlay for video playback; does using DirectX make a difference?

  8. Sudsy says:

    “but the underlying principle remains: DCs should not be allocated carelessly”

    (I don’t claim to know much about Windows API programming).

    When I was first learning about how to program the Windows API on Win 3.1 I took this advice to heart and always freed my DCs as soon as possible. This made sense considering all the memory limits in Windows 3.1.

    Now, Raymond, who clearly knows what he’s talking about, says “but the underlying principle remains: DCs should not be allocated carelessly”. In today’s virtual memory based Windows, why is this true? In other words, why are DCs, and the memory they require, more critical than any other kind of Windows API data structure? Why can’t they be allocated and managed dynamically? On a system with free memory why can’t you have a gazillion DCs?

    (Again, I’m asking because I’d like to know. I’m not questioning anybody’s knowledge).

    [You shouldn’t allocate anything carelessly. DCs are a subset of “anything”. -Raymond]
  9. Dave Behnke says:

    Interesting post Raymond.

    I have never created more than one DC for a Window.  Instead, I always end up selecting and deselecting the attributes that I need.  It’s just the way I think.

    Basically, I would end up selecting and deselecting the two different fonts within the for loop.  Maybe there are side-effects to this?

    It always seemed wasteful to use two DCs with different attributes.  However, that’s *part* of what a DC is for right?  

    Perhaps the FunnyDraw function could use SaveDC/RestoreDC to operate properly.

  10. Adam says:

    Semi-off-topic (i.e. not the point you were aiming for) but there’s
    another good reason to *always* release resources in the opposite order
    you acquired them. Although it wouldn’t fix the printing problem, the
    DC would at least have been returned in it’s original state if the
    cleanup was written as:

    SelectFont(hdc2, hfPrev2);

    ReleaseDC(hwnd, hdc2);

     

    SetTextAlign(hdc1, taPrev1);

    SelectFont(hdc1, hfPrev1);

    ReleaseDC(hwnd, hdc1);

    [Actually I did that on purpose specifically because it caused
    the “dirty DC” problem. A naive reading would say that we did clean up
    in reverse order – if you look at it on a per-DC basis. (Well, okay, I
    restored text alignment out of order.) -Raymond
    ]
  11. Jay B says:

    Wow, like the others, I’m surprised by the ability to use multiple DC’s… That could have made life easier for sure.

    SaveDC and RestoreDC were mentioned already… I’m surprised those aren’t more widely used as well.  When I first found those, it was a hallelujah moment.  Why bother keeping track of old objects yourself, making your code bulkier, more complex and potentially introduce bugs, when you can just reset it they way it was with one call before leaving a function.  It’s stack-based too.

  12. …I was just reading this earlier today:

    http://www.flounder.com/badprogram.htm#Using%20GetDC

    I
    think one point Flounder makes that’s a good one: using CClientDC
    rather than GetDC.  Obviously it’s a dumb mistake to not call
    ReleaseDC, but….people make mistakes.  I’d rather have scope
    handle the dirty work.  Your posted “FunnyDraw” method would be a
    few lines shorter, and more resiliant to changes by People Who Don’t
    Know Better (TM).

    If you didn’t want to use MFC, it’d be pretty trivial to make a small wrapper class that did the same thing as CClientDC

    [I think you missed the point of the article. CClientDC wouldn’t have helped any. -Raymond]
  13. Michael says:

    So under what circumstances *would* it be appropriate to use CS_OWNDC?

  14. Miral says:

    Most sample code that I’ve seen doesn’t bother to restore the DC to its original state (bitmaps, pens, fonts, etc) prior to releasing it.  They just assume that Windows will "reset" the DC when it’s no longer being used by anyone, just like everything else.

  15. Jeremy Noring says:

    I didn’t miss the point of the article–I just think you should use CClientDC rather than GetDC.  Sorry, it was a tangent.

    [I already explained multiple times
    why my samples do not use any class libraries. Plain C++ is the common
    language. If you want to use a particular dialect then more power to
    you. -Raymond
    ]
  16. Washu says:

    >So under what circumstances *would* it be appropriate to use CS_OWNDC?

    Typically OpenGL applications require CS_OWNDC. The general process would be to choose and set the pixel format of the DC, then bind the OpenGL Resource Context to the DC (using wglMakeCurrent).

  17. Shelley Fanboi says:

    The true history of Linux

    shelleytherepublican.com/2006/05/22/a-true-history-of-linux.aspx

  18. josh says:

    …and then you can’t destroy hf1 because it’s still selected into a device context, so you end up leaking GDI objects as well.

  19. PatriotB says:

    Ah yes, CS_OWNDC.  I used to work at a software company which produced massive VB6-based applications.  Virtually *all* of the app’s windows had CS_OWNDC (at least on NT–we didn’t support our app on 9x).  And people wondered why our app was reaching the limit of 10000 GDI handle per process…

  20. Billy says:

    PatriotB — VB 6 Forms and UserControls have a property called HasDC which sets the CS_OWNDC class bit when set to true.  Of course it defaults to true as well instead of false.  I can remember going through this issue for one of our apps that would absolutely smash the GDI heap limits on Win 9x.  This was in a large system with a large number of VB custom controls used on a large number of forms.

    I never figured out why they defaulted it to true.  It seems to be wrong.  If you are doing something specific that requires the class bit, you should know enough to turn it on…

  21. BryanK says:

    Billy — according to this:

    http://www.vbwm.com/art_1999/whatsnew/hasdc.asp

    it defaults to True so that the behavior is the same as in older VB versions (and so that the HDC property returns a value that can be cached; not that anyone should be using the HDC property anyway).

    Now as to why it was set that way in older VB versions, I’m not sure…

  22. Jeremy Noring says:

    Like I said in my first post, you don’t need to use a class library.

    [A
    wrapper class is just a special case of a class library. And it
    wouldn’t have helped here anyway. I guess I’m not sure what your point
    is. Yes, “FunnyDraw” would have been a few lines shorter with a wrapper
    class, but it would be just as buggy. -Raymond
    ]
  23. Suppose you are writing a graphics intensive application, like a photo editing app or a game, wouldn’t that be a good time to use CS_OWNDC? As long as you don’t try to get multiple DCs for your window, you should be fine, right? Or am I missing something?

    Also, I thought one of the benefits of CS_OWNDC was that you can get the DC once after you created the window and then use it throughout your application, without further calls to GetDC or BeginPaint. That’s pretty much what the documentation says. Is that correct?

  24. Sudsy says:

    “[You should allocate anything carelessly. DCs are a subset of “anything”. -Raymond]”

    I’ll presume that Raymond accidently left out the word “not” from this.
    But, the fact remains that there’s something unusual about DCs even in a virtual memory environment. It’s obvious that nothing should be allocated carelessly but I get the impression that it would be worse if I allocated 10,000 DCs than 10,000 buffers containing the string “The Old New Thing”. Right? Why?

    [Those 10,000 string buffers impact only your process. DCs are cross-process objects. It’s not the memory usage that’s the limiting factor; it’s the address space. -Raymond]
  25. Norman Diamond says:

    [The cache is per-thread so all you’re

    > corrupting is yourself. -Raymond]

    vs.

    > DCs are cross-process objects. It’s not the

    > memory usage that’s the limiting factor;

    > it’s the address space. -Raymond]

    Huh?  If they’re cross-process then didn’t Reinder still diagnose a problem properly?

    [DCs are cross-process. The DC cache is per-thread. There is no “vs” here. -Raymond]
  26. Norman Diamond says:

    [DCs are cross-process. The DC cache is

    > per-thread. There is no “vs” here. -Raymond]

    I’m still missing part of this.

    If a DC isn’t cleaned up before being released, then a corrupted DC
    goes into a per-thread cache.  Therefore it seems likely that all
    released DCs, corrupted or not, go into a per-thread cache.

    When do DCs become cross-process objects?  When they’re
    acquired they become cross-process but when they’re released they go
    into a per-thread cache?  (I sure hope not, I sure hope this
    logical inference is as illogical to you as it is to me.)  Do
    CS_OWNDC windows do something to turn their DCs into cross-process
    objects?

    And Reinder’s question still seems relevant, if DCs are cross-process objects.

    [Well, some experimentation tells me that DCs are not cross-process after all (they were in Windows 3.1), so I apologize for the misinformation. I am not a substitute for formal documentation.

    By “cross-process” I meant that a DC handle, once created, is valid in any process. (This was true in Windows 3.1 though apparently not any more.) The handle doesn’t “become” cross-process any more than a window handle “becomes” cross-process. The DC cache is per-thread (or at least was in Windows 3.1; things may be different nowadays). If a thread calls “GetDC”, it looks in the cache of DCs recently released by that thread. The two concepts (visibility and cache affinity) are unrelated. Passports are per-country but are globally valid – nobody seems confused by that. -Raymond]

  27. Norman Diamond says:

    I apologize for the misinformation.

    Accepted, and thank you for the corrections.

    > I am not a substitute for formal

    > documentation.

    Yeah, well, formal documentation is no substitute for you either.
     I think in the thread that started one day after this one,
    someone guessed that your reason for posting this series is that you’re
    not authorized to fix the formal documentation.

    [This type of article is not suitable for
    formal documentation. For one thing, it’s too informal. For another
    thing, formal documentation is about explaining what each flag does and
    letting you decide which ones you want. If you want to use CS_CLASSDC
    then more power to you. Formal documentation is not going to say
    “CS_CLASSDC is hard to use correctly, so be careful.” -Raymond
    ]

    > By “cross-process” I meant that a DC handle,

    > once created, is valid in any process [in

    > Windows 3.1].  […]  If a thread calls

    > “GetDC”, it looks in the cache of DCs

    > recently released by that thread.

    So in 3.1, if this thread’s cache was empty and GetDC had to take a
    DC that isn’t from this thread’s cache, it still could have obtained a
    corrupted DC that a different thread released?

    [What part of “per-thread” don’t you
    understand? If one thread could look in another thread’s cache, then
    it’s not a per-thread cache, now, is it? I can’t tell if you’re
    genuinely confused or are being intentionally obtuse to prove some
    point. -Raymond
    ]

    Meanwhile…

    > Passports are per-country but are globally

    > valid

    Wanna bet?

    [I should have know that you of all people would pointlessly nitpick this analogy. -Raymond]

    > nobody seems confused by that

    Wanna bet?

  28. BryanK says:

    So the way I understand what you’re saying, thread 2 always gets a
    different DC than thread 1; thread 2’s DC will never be dirty (have the
    wrong object(s) selected into it) unless thread 2 left it that way
    itself.

    [Or unless you’re using CS_OWNDC or CS_CLASSDC, which was the point of this mini-series. -Raymond]

    Not that this excuses any practice of releasing dirty DCs, but at least you won’t be breaking anyone else’s window.

    Actually, re-reading the comments here, it seems you said that at
    least once before, way back up under Reinder’s comment June 1.
     Hmm.

  29. BryanK says:

    I *think* Norman meant a scenario like this (if not, I’m wondering what happens):

    Thread 1 calls GetDC, selects some object into the returned DC, and
    releases it.  The DC gets put back into thread 1’s cache, in a
    “dirty” state.

    Then, thread 2 gets a DC for the same window (…is this even
    legal, since the window is owned by thread 1?).  No DC for that
    window exists in thread 2’s cache.  Does it get the dirty DC that
    thread 1 released, or does the window manager create a new DC?  If
    thread 2 gets the dirty DC, does it get a clean one after the dirty one
    ages out of thread 1’s cache?

    [Thread 2 can’t see thread 1’s cache. There could be a
    kangaroo in thread 1’s cache, it won’t have any effect on thread 2.
    Note that the fact that the cache is per-thread is an implementation
    detail and can change at any time. In fact, it may have already changed
    and I simply haven’t noticed. -Raymond
    ]
  30. Norman Diamond says:

    >> By “cross-process” I meant that a DC
    >>> handle, once created, is valid in any
    >>> process [in Windows 3.1].  […]  If a
    >>> thread calls “GetDC”, it looks in the
    >>> cache of DCs recently released by that
    >>> thread.
    >>
    >> So in 3.1, if this thread’s cache was empty
    >> and GetDC had to take a DC that isn’t from
    >> this thread’s cache, it still could have
    >> obtained a corrupted DC that a different
    >> thread released?
    >
    > [What part of “per-thread” don’t you
    > understand?

    Maybe the part that turns a cache into an infinite supply?  You said there’s a per-thread cache and that a released DC goes into the thread’s cache.  Well the same thread is capable of getting DCs again.  After it’s gotten all the DCs that it previously released, its cache is empty, right?  When it tries to get another DC, can it get one?

    When a thread exits, do its corrupted cached DCs get leaked or do they rejoin a pool of available DCs?

    If all currently unused DCs are on the caches of other threads, and our thread tries to get another DC, does Windows inherit a famous old DEC error message?  (“Memory available, but not for you.”)  Or does Windows take a DC in order to get it usable where needed?

    And still, still, if DCs are cross-process then Reinder’s question still looks mighty valid.

    [This article was not about the details of the DC cache. The cache has a policies to cover these cases and they are pretty much in line with most other cache policies And I already answered Reinder’s question: The cache is per thread. All you’re corrupting is yourself. -Raymond]
  31. Miral says:

    > Formal documentation is not going to say "CS_CLASSDC is hard to use

    > correctly, so be careful."

    Why not?  If it’s true, it should.  Many people are only ever exposed to the formal documentation, and don’t see the informal docs that say "but wait, you shouldn’t use it like *this*".

  32. Norman Diamond says:

    I agree with Miral.  In fact some pages in MSDN say that certain items are defined only for backwards compatibility with 16-bit Windows and new applications should not use them.  I think such notices have even been added to more pages recently, belatedly but better than never.

Comments are closed.

Skip to main content