Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Design

Undocumented Corner


APR96: UNDOCUMENTED CORNER

Scot is a cofounder of Stingray Software, an MFC extension company. He can be contacted at [email protected]. George is a senior computer scientist with DevelopMentor where he develops and delivers courseware for developers using MFC and OLE. George can be contacted at [email protected]. They are the coauthors of MFC Internals (Addison-Wesley, 1996).


MFC comes with full source code and a great set of online documentation. However, while writing our book, MFC Internals, we discovered a plethora of interesting undocumented classes, functions, and MFC behavior. Since then, we've spent a great deal of time learning how these undocumented aspects of MFC work, what they do, and documenting them.

Microsoft only documents the non-implementation portions of MFC so that it can change the implementation details from release to release. As a C++ class library provider, this is desirable since it allows the maximum flexibility to change classes around from release to release. However, MFC programmers will find themselves having to decipher undocumented MFC behavior time and time again when writing MFC applications that push the bounds of the MFC documentation. For example, have you ever ended up in the middle of undocumented MFC classes when debugging? Or have you ever tried to customize the MFC print-preview engine? Do you need to know how MFC OLE Automation is implemented so you can extend it? How about OLE documents or OLE controls?

In this series of articles, we will expose interesting undocumented MFC behavior discovered during our many MFC spelunking sessions and in the process answer many of the aforementioned questions. In addition, we will show you how to exploit the undocumented features in your MFC applications. Whenever possible, the MFC source-code filename will be mentioned so you can follow along in your editor, or do some further exploring on your own.

Of course, all undocumented disclaimers apply--Microsoft can (and probably will) change undocumented areas of MFC, so use any undocumented details you find here with caution and at your own risk. Also, for the time being, we'll always refer to the latest version of MFC (currently 4.0). Older versions of MFC do behave differently than what we're describing--more proof that the undocumented MFC internals always are in flux. We also would like to note that Microsoft (specifically the MFC team) has been very gracious in helping us discover some of the more obscure behavior of MFC. In particular, we would like to thank Dean McCrory, MFC lead at Microsoft, for his valuable time and insight.

Interesting Undocumented CDocument Behavior

MFC 4.0 introduced an improvement to the CDocument class. The new virtual member functions, GetFile() and ReleaseFile(), let you specify a specialized CFile derivative in a CDocument derivative. Whenever CDocument needs to do something with a file, it calls GetFile() with the filename, file permissions, and some other arguments. GetFile() returns a CFile pointer, which can be any CFile derivative. This includes CDocument::Serialize(). The undocumented fun begins when we take a peek at the default implementation of CDocument::GetFile() in Listing One.

The implementation of GetFile() is pretty close to what you would imagine, except for the first line. What is this undocumented CMirrorFile class? Why is your document using it instead of CFile? What implications does this have for your programs? Is CMirrorFile connecting to the Microsoft Network and sending your documents to Microsoft?

CMirrorFile Exposed

A quick search reveals that CMirrorFile is declared in the AFXPRIV.H header file. Listing Two contains the declaration of this CFile derivative.

This certainly is a minimal CFile derivative. Aside from overriding Open(), Close(), and Abort(), CMirrorFile adds one data member, m_strMirrorName.

The implementation for CMirrorFile lives with most of CDocument's source in DOCCORE.CPP. Listing Three shows the pseudocode for CMirrorFile::Open().

CMirrorFile::Open() breaks into two logical blocks. In the first, Open() checks for the modeCreate indicating that the caller wants to create a new file or truncate an existing file. Next, Open() calls CFile::GetStatus(). GetStatus() returns nonzero if the file exists (which implies that the user wants to truncate it). If the file exists, Open() then calls GetDiskFreeSpace() and determines how many bytes are available on the drive. Open() then compares this result with two times the size of the existing file. If there is enough room for two times the size of the current filename, Open() creates a temporary file and stores the name in the m_strMirrorName.

The second block of code in Open() only runs if m_strMirrorName is not empty, which implies that the contents of the first block ran and obtained a temporary file. In the second block, if there is a non-empty m_strMirrorName, Open() goes ahead and opens the mirror file using CFile::Open(). Next, Open() copies the file time and file security from the original file to the mirror file. If the second block has executed, Open() returns True. If the second block does not execute, Open() calls right through to CFile::Open(), returning whatever it returns.

To summarize, CMirrorFile::Open() is actually opening a different file than the one specified if a write operation is going on CFile::modeCreate and if a file is being overwritten.

For example, in Scribble (which uses document/view), if you wrote a fresh scribble and saved it in UNDOCMFC.SCR, the code would not execute. However, if you load the UNDOCMFC.SCR file, make some changes, and then save, this code will execute and Scribble will actually write to a mirror file.

Turning our attention to document data, Listing Four contains the pseudocode for CMirrorFile::Close() from DOCCORE.CPP.

First, Close() stores the name of the file in m_strName (a deceptively named variable) because the CFile::Close() call clears out m_strFileName.

After calling CFile::Close(), CMirrorFile::Close() checks to see if there is a mirror file in use. If so, Open() deletes the specified file and copies the mirror file over to the specified file.

In a nutshell, CMirrorFile is protecting your document by saving the original and writing to a temporary file. If there is a problem writing, the original file is safe and sound. If there isn't a problem, CMirrorFile copies the new file over the file and you are none the wiser (until now). CMirrorFile even makes sure that the original security and file-creation information is copied over correctly.

Some of you may not like MFC doing something like this behind your back. Additionally, there are the cycles lost checking the disk space, copying the security/file-time information, and copying the mirror file over the original file. If you don't like the fact that CDocument uses CMirrorFile, you can override GetFile() to return a good-old-fashioned vanilla CFile.

CMirrorFile really is the only undocumented CDocument behavior of interest. Next, we examine the undocumented CView derivative CPreviewView and how it implements MFC print previewing.

Undocumented Print Preview

MFC's print-preview support is based on the document/view architecture. The key advantage to MFC document/view is that the normal display-drawing code in your view is transparently reused to provide basic print-previewing functionality. The default MFC print preview supports one- and two-page display and three-level zooming. Figure 1 shows the MFC print-preview engine in a zoomed two-page state. Notice how the user has a toolbar and the application's normal menu and view are replaced by the print-preview view.

We first discovered CPreviewView while tracing the MFC print-preview logic, which starts in CView::DoPrintPreview(). Listing Five contains the CView::DoPrintPreview() pseudocode so you can see how CPreviewView is created, initialized, and used.

DoPrintPreview() first stores a pointer to the main frame window in pParent. Next, it creates a local CCreateContext structure and populates its fields.

Next, DoPrintPreview() creates a CPreviewView instance with dynamic creation using the CRuntimeClass pointer argument. DoPrintPreview() then calls CFrameWnd ::OnSetPreviewMode() and creates a toolbar based on the toolbar resource argument.

After creating and storing the toolbar in the CPreviewView pointer, DoPrintPreview() creates the view and makes it the active view. When setting the CPreviewView to the active view, DoPrintPreview() saves the previously active view in the CPrintPreviewState structure. Once the CPreviewView and toolbar are created and activated, DoPrintPreview() updates them and returns True.

We've discovered a good deal about print preview. DoPrintPreview() sets the active view in the main frame to switch from the regular MDI or SDI window to the print-preview view. You could use this technique in your MFC applications if you ever wanted to have a "full window mode" or wanted to produce a similar effect. To learn more about how it works, check out CFrameWnd::OnSetPreviewMode().

While it's pretty clear what DoPrintPreview() is doing, it is still not clear how print preview actually works. To understand MFC print preview, you have to get to know CPreviewView.

CPreviewView Revealed

Inside MFC's private header file, AFXPRIV.H, we found the declaration for CPreviewView, which reveals a great deal about the class. Listing Six contains the abbreviated declaration.

Notice that CPreviewView is a CScrollView derivative. We haven't covered this class, but as the name implies, CScrollView provides scrolling support. When you zoom in on a print preview, you'll notice it displays scroll bars, that's the CScrollView inheritance at work.

CPreviewView is brimming with interesting behavior. Table 1 describes the uses for key data members. SetPrintView(), called in CView::DoPrintPreview() (see Listing Five), is one important member for CPreviewView. It takes care of initializing the print-preview view and preparing it to start the print-previewing process. Keep that in mind as you review the pseudocode for CPreviewView::SetPrintView(), as found in VIEWPREV.CPP (see Listing Seven).

SetPrintView() first sets the value of m_pPrintView to the document's view. The document's view actually does the rendering in OnDraw(). CPreviewView keeps a copy of the document's live view because CPreviewView uses the view's OnDraw() function to render the data on the preview screen.

Next, SetPrintView() creates a new CPrintInfo object. It's not really obvious from the pseudocode, but the CPrintInfo constructor creates a CPrintDialog (a Windows printing common dialog). After creating the CPrintInfo object, SetPrintView() sets some flags. The most important flag tells the CPrintDialog not to return a DC. You'll see why in a second.

After creating the CPrintInfo object, SetPrintView() creates a CPreviewDC object. CPreviewDC is yet another undocumented MFC class. MFC's CDC class contains two handles to device contexts: m_hDC and m_hAttribDC. The m_hDC member represents the device context for the output and the m_hAttribDC is used for information purposes. Usually these device contexts represent the same thing, the screen or the printer.

The CPreviewDC object maintains two different device contexts--one for the screen (m_hDC) and one for the printer (m_hAttribDC). CPreviewDC is implemented this way because print preview has to draw something as though it would appear on the printer, but must be able to translate so it appears correctly on the screen. SetPrintView() sets up the CPreviewDC accordingly (setting the m_hDC to the view's hDC and setting m_hAttribDC to the printer's hDC). To learn more about how CPreviewDC handles displaying what the printer will display onscreen, check out the MFC header AFXPRIV.H and the source file DCPREV.CPP.

After initializing the DCs to their proper print-preview state, SetPrintView() initializes the m_sizePrinterPPI data member and m_nPages and m_nZoomOutPages. Then, SetPrintView() initializes the scroll bars in the scroll view and sets the current page.

At this point, we still haven't seen where the actual drawing of the print preview happens. If you'll look back at Listing Seven (CView::DoPrintPreview()), you'll notice that this routine "forces" an update of the frame window that contains the freshly created CPreviewView. Like any other MFC view, this will cause CPreviewView's OnDraw() member function to be called.

First, OnDraw() creates two pens. rectPen is for the box that is drawn to represent the page. shadowPen is used to draw a shadow around the page and give it a fancy three-dimensional effect.

Next, OnDraw() enters a For loop for each page, taking the following steps:

    1. It draws the page outline and shadow using the paint DC that is passed as an argument to OnDraw(). It draws the rectangle with a series of MoveTo()/LineTo() calls. Then the For loop calls FillRect() to draw in the white color of the pages.

    2. It displays the page number by calling OnDisplayPageNumber().

    3. Once the fresh piece of paper has been drawn, OnDraw() calls m_pPrintView->OnPrint() with the CPreviewDC, thus generating the print-preview output. OnPrint() calls your view's OnDraw() code with a CPreviewDC that reflects the printed output on the display as a print preview.

After the For loop has iterated through all of the pages to be previewed, OnDraw() frees the pens it created.

Recap

Now that you know how CPreviewView and CPreviewDC work, the big question is how do you apply this knowledge? In our next installment, we'll show you how to customize the MFC print preview and we'll also introduce a couple of new undocumented MFC classes.

Figure 1: MFC print-preview engine in a zoomed two-page state.

Table 1: CPreviewView data members.

Member               Description

m_pOrigView          Pointer to the previously active view.
m_pPrintView         Pointer to the view for "printing."
m_pPreviewDC         Pointer to a CPreviewDC.
m_dcPrint            Pointer to a printer DC.
PAGE_INFO            Structure that defines the dimensions of one of the preview pages.
m_pPageInfo          Pointer to a page info structure.
m_pageInfoArray      Array of two-page info structures; hard-coded to 2.
m_bPageNumDisplayed  Flag that indicates if the page number has been displayed.
m_nZoomOutPages      Stores the number of pages to be displayed on one print-preview view. 
m_nZoomState         Can be one of four zoom states (set via SetZoomState()).
  ZOOM_OUT           Default, shows a smaller than 1:1 ratio. 
  ZOOM_MIDDLE        Zoom level between out and in. 
  ZOOM_IN            Shows a larger than 1:1 ratio. 
  ZOOM_OFF           No zooming.
m_nMaxPages          Specifies the number of pages (always 2 for CPreviewView).
m_nCurrentPage       Current page being displayed.
m_nPages             Number of pages. Can be either one or two in the default CPreviewView.
m_nSecondPageOffset  Specifies the left coordinate of where the second preview page is drawn.
m_hMagnifyCursor     Handle to the magnifying glass cursor.
m_sizePrinterPPI     Stores the dimensions of a page on the printer; retrieved by calling 
                         GetDeviceCaps(LOGPIXELSX).
m_ptCenterPoint      Used to center the print preview pages on the page.
m_pPreviewInfo       Pointer to a CPrintInfo structure that is used to store printing 
                         information.

Listing One

CFile* CDocument::GetFile(LPCTSTR lpszFileName, UINT nOpenFlags, 
                                                     CFileException* pError)
{
     CMirrorFile* pFile = new CMirrorFile;
     if (!pFile->Open(lpszFileName, nOpenFlags, pError))
              delete pFile; pFile = NULL;
     return pFile;
}

Listing Two

class CMirrorFile : public CFile
{
// Implementation
public:
    virtual void Abort();
    virtual void Close();
    virtual BOOL Open(LPCTSTR lpszFileName, UINT nOpenFlags,
    CFileException* pError = NULL); protected:
    CString m_strMirrorName;
};

Listing Three

BOOL CMirrorFile::Open(LPCTSTR lpszFileName, UINT nOpenFlags, 
                                                    CFileException* pError)
{
     m_strMirrorName.Empty();
     CFileStatus status;
     if (nOpenFlags & CFile::modeCreate) {
          if (CFile::GetStatus(lpszFileName, status)){
              CString strRoot;
              AfxGetRoot(lpszFileName, strRoot);
              DWORD dwSecPerClus, dwBytesPerSec, dwFreeClus, dwTotalClus;
              int nBytes = 0;
              if (GetDiskFreeSpace(strRoot, &dwSecPerClus, &dwBytesPerSec, 
              &dwFreeClus, &dwTotalClus)){
                   nBytes = dwFreeClus*dwSecPerClus*dwBytesPerSec;
                   if (nBytes > 2*status.m_size){
                   // get the directory for the file TCHAR 
                   szPath[_MAX_PATH];
                   LPTSTR lpszName;
                   GetFullPathName(lpszFileName,_MAX_PATH, szPath, &lpszName);
                   *lpszName = NULL;
                   GetTempFileName(szPath, _T(MFC), 0,
                           m_strMirrorName.GetBuffer(_MAX_PATH+1)); 
                   m_strMirrorName.ReleaseBuffer();
        }
       }
     }
     if (!m_strMirrorName.IsEmpty() &&
         CFile::Open(m_strMirrorName, nOpenFlags, pError)){ 
              m_strFileName = lpszFileName;
              FILETIME ftCreate, ftAccess, ftModify; 
              if (::GetFileTime((HANDLE)m_hFile,&ftCreate,&ftAccess,ftModify)){
                  AfxTimeToFileTime(status.m_ctime, &ftCreate);
                  SetFileTime((HANDLE)m_hFile,&ftCreate,&ftAccess, &ftModify);
     }
     DWORD dwLength = 0;
     PSECURITY_DESCRIPTOR pSecurityDescriptor = NULL;
     GetFileSecurity(lpszFileName, DACL_SECURITY_INFORMATION,NULL, 
                                                          dwLength, &dwLength);
     pSecurityDescriptor = (PSECURITY_DESCRIPTOR) new BYTE[dwLength];
     if (::GetFileSecurity(lpszFileName, DACL_SECURITY_INFORMATION,
                   pSecurityDescriptor, dwLength, &dwLength)){
                   SetFileSecurity(m_strMirrorName, DACL_SECURITY_INFORMATION, 
                              pSecurityDescriptor); 
     }
     delete[] (BYTE*)pSecurityDescriptor;
     return TRUE;
  }
  m_strMirrorName.Empty(); 
  return CFile::Open(lpszFileName, nOpenFlags, pError);
}

Listing Four

void CMirrorFile::Close()
{
     CString m_strName = m_strFileName;
     CFile::Close();
     if (!m_strMirrorName.IsEmpty()) {
        CFile::Remove(m_strName);
        CFile::Rename(m_strMirrorName, m_strName);
    }
}

Listing Five

BOOL CView::DoPrintPreview(UINT nIDResource, CView* pPrintView, CRuntimeClass*
                                 pPreviewViewClass, CPrintPreviewState* pState)
{
      CFrameWnd* pParent = (CFrameWnd*)AfxGetThread()->m_pMainWnd;
      CCreateContext context;
      context.m_pCurrentFrame = pParent;
      context.m_pCurrentDoc = GetDocument();
      context.m_pLastView = this;
      // Create the preview view object
      CPreviewView* pView = (CPreviewView*)pPreviewViewClass->CreateObject();
      pView->m_pPreviewState = pState;        // save pointer
      pParent->OnSetPreviewMode(TRUE, pState);    // Take over Frame Window
      // Create the toolbar from the dialog resource
      pView->m_pToolBar = new CDialogBar;
      if (!pView->m_pToolBar->Create(pParent, MAKEINTRESOURCE(nIDResource),
                                              CBRS_TOP, AFX_IDW_PREVIEW_BAR)){
            TRACE0(Error: Preview could not create toolbar dialog.\n); 
            return FALSE;
      }
      pView->m_pToolBar->m_bAutoDelete = TRUE;    // automatic cleanup
      if (!pView->Create(NULL, NULL, AFX_WS_DEFAULT_VIEW,
                  CRect(0,0,0,0), pParent, AFX_IDW_PANE_FIRST, &context)) { 
                  TRACE0(Error: couldnt create preview view for frame.\n); 
                  return FALSE;
      }
      pState->pViewActiveOld = pParent->GetActiveView();
      CView* pActiveView = pParent->GetActiveFrame()->GetActiveView();
      pActiveView->OnActivateView(FALSE, pActiveView, pActiveView);
      pView->SetPrintView(pPrintView);
      pParent->SetActiveView(pView);  // set active view - even for MDI
      // update toolbar and redraw everything 
      pView->m_pToolBar->SendMessage(WM_IDLEUPDATECMDUI, (WPARAM)TRUE); 
      pParent->RecalcLayout();            // position and size everything 
      pParent->UpdateWindow();
      return TRUE;
}

Listing Six

class CPreviewView : public CScrollView
{
      DECLARE_DYNCREATE(CPreviewView)
// Constructors
public:
      CPreviewView();
      BOOL SetPrintView(CView* pPrintView);
// Attributes
protected:
      CView* m_pOrigView;
      CView* m_pPrintView;
      CPreviewDC* m_pPreviewDC;  // Output and attrib DCs Set, not created
      CDC m_dcPrint;             // Actual printer DC
// Operations *omitted
// Overridables *omitted
// Implementation * some omitted
public:
      virtual void OnPrepareDC(CDC* pDC, CPrintInfo* pInfo = NULL);
protected:
      afx_msg void OnPreviewClose(); 
      afx_msg int  OnCreate(LPCREATESTRUCT lpCreateStruct); 
      afx_msg void OnSize(UINT nType, int cx, int cy); 
      afx_msg void OnDraw(CDC* pDC); 
      afx_msg void OnLButtonDown(UINT nFlags, CPoint point); 
      afx_msg BOOL OnEraseBkgnd(CDC* pDC); 
      afx_msg void OnNextPage(); 
      afx_msg void OnPrevPage(); 
      afx_msg void OnPreviewPrint(); 
      afx_msg void OnZoomIn(); 
      afx_msg void OnZoomOut();
      void DoZoom(UINT nPage, CPoint point);
      void SetScaledSize(UINT nPage);
      CSize CalcPageDisplaySize();
      CPrintPreviewState* m_pPreviewState; // State to restore
      CDialogBar* m_pToolBar; // Toolbar for preview
      struct PAGE_INFO {
           CRect rectScreen; // screen rect (screen device units)
           CSize sizeUnscaled; // unscaled screen rect (screen device units)
           CSize sizeScaleRatio; // scale ratio (cx/cy)
           CSize sizeZoomOutRatio; // scale ratio when zoomed out (cx/cy)
      };
      PAGE_INFO* m_pPageInfo; // Array of page info structures
      PAGE_INFO m_pageInfoArray[2]; //Embedded array for default implementation
      BOOL m_bPageNumDisplayed;  // Flags whether or not page number has yet
                                 // been displayed on status line 
      UINT m_nZoomOutPages; // number of pages when zoomed out 
      UINT m_nZoomState;
      UINT m_nMaxPages; // for sanity checks
      UINT m_nCurrentPage;
      UINT m_nPages;
      int m_nSecondPageOffset; // used to shift second page position
      HCURSOR m_hMagnifyCursor;
      CSize m_sizePrinterPPI; // printer pixels per inch
      CPoint m_ptCenterPoint;
      CPrintInfo* m_pPreviewInfo;
      DECLARE_MESSAGE_MAP()
};

Listing Seven

BOOL CPreviewView::SetPrintView(CView* pPrintView)
{
    m_pPrintView = pPrintView;
    m_pPreviewInfo = new CPrintInfo;
    m_pPreviewInfo->m_pPD->SetHelpID(AFX_IDD_PRINTSETUP);
    m_pPreviewInfo->m_pPD->m_pd.Flags |= PD_PRINTSETUP;
    m_pPreviewInfo->m_pPD->m_pd.Flags &= ~PD_RETURNDC;
    m_pPreviewInfo->m_bPreview = TRUE;  // signal that this is preview
    m_pPreviewDC = new CPreviewDC;      // must be created before any
    if (!m_pPrintView->OnPreparePrinting(m_pPreviewInfo))
          return FALSE;
    m_dcPrint.Attach(m_pPreviewInfo->m_pPD->m_pd.hDC);
    m_pPreviewDC->SetAttribDC(m_pPreviewInfo->m_pPD->m_pd.hDC);
    m_pPreviewDC->m_bPrinting = TRUE;
    m_dcPrint.m_bPrinting = TRUE;
    m_dcPrint.SaveDC();     // Save pristine state of DC
    HDC hDC = ::GetDC(m_hWnd);
    m_pPreviewDC->SetOutputDC(hDC);
    m_pPrintView->OnBeginPrinting(m_pPreviewDC, m_pPreviewInfo); 
    m_pPreviewDC->ReleaseOutputDC();
    ::ReleaseDC(m_hWnd, hDC);
    m_dcPrint.RestoreDC(-1);    // restore to untouched state
    // Get Pixels per inch from Printer 
    m_sizePrinterPPI.cx = m_dcPrint.GetDeviceCaps(LOGPIXELSX); 
    m_sizePrinterPPI.cy = m_dcPrint.GetDeviceCaps(LOGPIXELSY);
    m_nPages = m_pPreviewInfo->m_nNumPreviewPages; 
    m_nZoomOutPages = m_nPages;
    SetScrollSizes(MM_TEXT, CSize(1, 1));   // initialize mapping mode only
    if (m_pPreviewInfo->GetMaxPage() < 0x8000 &&
         m_pPreviewInfo->GetMaxPage() - m_pPreviewInfo->GetMinPage() <= 32767U)
            SetScrollRange(SB_VERT, m_pPreviewInfo->GetMinPage(),
            m_pPreviewInfo->GetMaxPage(), FALSE);
    else
         ShowScrollBar(SB_VERT, FALSE);
    SetCurrentPage(m_pPreviewInfo->m_nCurPage, TRUE); return TRUE;
}


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.