Thread affinity of user interface objects: Addendum


Some time ago, I discussed the thread affinity of device contexts, noting that the Get­DC and Release­DC functions must be called from the same thread.

The same restriction also applies to printer DCs. Printer DCs must be created and destroyed on the same thread. The printing team imposed this additional rule in order to make it easier for printer driver vendors to author their drivers. (Printer driver developers have a habit of calling CoInitialize in their implementation of CreateDC and CoUninitialize in their implementation of DeleteDC.)

Given that printer drivers are a third-party extension point, it's probably in your best interest to treat printer DCs as having hard thread affinity, since who knows what the printer driver is going to do if you try to access it from multiple threads. In other words, the thread that creates the printer DC should be the only thread which prints to it, and should also be the thread which destroys the printer DC when printing is complete.

That's not saying that all your printing has to be done from one thread. If you want, you can create multiple printer DCs, each on different threads. Just make sure to use each printer DC only on the thread that created it. Your printer driver will thank you.

Comments (14)
  1. John Elliott says:

    "(Printer driver developers have a habit of calling CoInitialize in their implementation of CreateDC and CoUninitialize in their implementation of DeleteDC.)"

    Which had amusing consequences in an MFC 6.0 program I maintained that didn't use COM: the MFC CPrintDialog works by hooking window creation, calling ::PrintDlg(), and subclassing the next window to be created. In this case, that turned out to be a hidden window created by CoInitialize, rather than the Windows common dialog it was expecting.

  2. Joshua says:

    @John Elliott: Man that gun was pointed at the foot for a long time. Maybe MFC should be relegated to the trash heap. Oh wait, that's already been done.

  3. me says:

    I call that the win32 call/anti-call rule.  Unless explicitly called out in the docs treat it as if it can only be used on 1 thread…  Your code will thank you for it.  The subtle crashes by messing it up are quite annoying to track down…

  4. Henning Makholm says:

    "Printer driver developers have a habit of calling CoInitialize in their implementation of CreateDC" — hmm, does that imply that it is not safe to print from a thread that already belongs to the multithreaded apartment? That wasn't apparent from the documentation of CreateDC. (Nor does it seem to be stated in the general introduction to printing parts of MSDN, as far as a cursory skim goes).

  5. Joshua says:

    Microsoft will never get rid of the bad reputation from programmers until they build a stack of replacement to current COM interfaces that don't load foreign forth-party DLLs into third-party processes by default. This path is madness.

  6. Erik Knowles says:

    Henning,

    Bingo! I'd already tried to post a comment regarding HP's cruddy drivers but my comment disappeared. (Mmm…does this blog not like Firefox?) Anyways, post-printing, some HP drivers stupidly call COUninitialize() *regardless* of whether their COInitialize call succeeded (it will fail with RPC_E_CHANGED_MODE if COM has already been set up as multithreaded and the COM load/unload counter will not be incremented).

    Post-printing, since the driver's COInitialize() failed the COUninitialize() is unbalanced (the COM load counter is decremented without it being incremented by COInitialize) and the result is that the COM system is uninitialized by the *@#% driver. Poof, any COM objects held by your app are gone.

  7. Neil says:

    I've always wondered who to blame when an application only crashes when printing to one particular driver. (In my case I think I worked around it by launching the application from WinDbg -G -g with a saved workspace with a breakpoint on the faulting instruction that jumped over it…)

  8. Henning Makholm says:

    @Erik: I'll admit that I had to read "To close the COM library gracefully, each successful call to CoInitialize or CoInitializeEx, including those that return S_FALSE, must be balanced by a corresponding call to CoUninitialize" in the documentation carefully several times before I noticed that it is only SUCCESSFUL calls that must be balanced with CoUninitialize. It is very easy to misinterpret "including those that return S_FALSE" as "even those that fail". Bad naming for a successful return code, I think.

  9. Jon says:

    (I'll copy and paste this into the next suggestion box, but I copy it here in case you're tempted to answer it.)

    In Windows 7 I can't pin the "Microsoft Help Viewer" that comes with Visual Studio 2012 to the taskbar, and its jump list only contains a "Close" option (none of the other usual ones). I have never seen this behaviour from any other window.

    In Windows 8 I can pin it.

    So, what was wrong and what changed?

    [This article explains what was wrong and some time ago this one how you can work around it. I'm not sure what you mean by "what changed?" What changed is that you can now pin it. -Raymond]
  10. Yuhong Bao says:

    Erik Knowles: Fortunately, most GUI threads are STA.

  11. Yuhong Bao says:

    In fact, I recommend STA for GUI threads and MTA for non-GUI threads.

  12. ender says:

    Ugh, HP printer drivers. I've seen a bunch of problems because of them, weird error/crash messages, random garbage printed at set time and uninstallers that don't do anything (it's gotten bad enough that many of the recent drivers ship with programs that manually uninstall older driver versions, because the only other way is to use MS FixIt and have that remove 20-30 separate packages they install).

    @Erik: the blog will eat your comment if it's been too long between the page loaded and you pressing Post – best to copy the comment to clipboard, refresh the page and then post it.

  13. Erik Knowles says:

    Yohong Bao: Unfortunately, our apps generate printed reports in the background… in a non-gui MTA thread.

  14. 640k says:

    Can someone PLEASE increase the session time-out for posting on this blog?

Comments are closed.