Features


Safer Graphical Drawing with MFC

Ivan J. Johnson

Exceptions can really mess up the state of a graphic display, unless you preserve it with a well placed destructor.


The following code shows a common MFC idiom:

void CMyView::OnDraw(CDC* pDC)
{
   CPen* pOldPen =
      pDC->SelectObject(&pen);
   DoSomeDrawing(pDC);
   pDC->SelectObject(pOldPen);
}

Such code is ubiquitous in books and Microsoft samples, but unfortunately it is not exception-safe. If DoSomeDrawing should trigger an exception, the system's pen will not be restored, with unpredictable results. (Keep in mind that even if DoSomeDrawing contains no explicit throw statements, an exception could still be thrown by the language itself or by a library function called by DoSomeDrawing. Also, VC++ will throw a "hardwaqre exception" for certain errors, such as dereferencing an invalid pointer or dividing by zero.)

Fortunately, there is a simple fix. First, include the code from Figure 1 via some header file. Second, rewrite the drawing code as follows:

void CMyView::OnDraw(CDC* pDC)
{
   AutoSelector a(pDC, &pen);
   DoSomeDrawing(pDC);
}

Not only is this safer, it's also more concise and just as efficient. AutoSelector applies the "resource acquisition is initialization" idiom [1] to save and restore context. The constructor selects the new pen and saves a pointer to the old one in member data. Then, when OnDraw exits, whether by normal return or due to an exception, AutoSelector's destructor automatically restores the old pen.

You can also use AutoSelector for other GDI objects, like brushes and fonts. Stock objects are allowed also:

AutoSelector a(pDC, GRAY_BRUSH);

The only GDI object you can't use is CRgn, a special case in MFC [2]. AutoSelector will not work with CRgn, and it's not needed anyway. AutoSelector prevents this usage with a compile-time error when possible and with an assertion when necessary.

One other caveat: AutoSelector's internally stored pointer to the old GDI object is valid only during the processing of a single Windows message. You should ensure that the creation and destruction of an AutoSelector object occurs during the processing of the same message by making it an automatic variable in OnDraw or any message handler function (and not a data member of your view class). Otherwise, the class might be left with a dangling pointer [2].

Notes and References

[1] Bjarne Stroustrup. The C++ Programming Language, 3rd Ed. (Addison-Wesley, 1997), section 14.4.1.

[2] See MSDN topic "CDC::SelectObject" for details.

Ivan J. Johnson is a software engineer and consultant in Sacramento, CA. He can be reached at ijohnson@alum.mit.edu.