An MFC Class Map Utility |
|
So what to do when trapped on an airplane at 36,000 feet? Answer: write a new little utility.
This came about because I wanted to capture the class structure I used in the CPUID project. So I turned on the browser information (something I normally find totally useless) and went to look for the class map it used to produce. News break: it doesn't produce a class map view any longer! This wasn't a total loss because the printed output it used to produce was amateurish at best. So I decided to write my own. Here it is.
Note that this represents about 8,000 lines of source code, but of this, only about 2000 lines are relevant to the problem at hand, and of those about 1600 are actually original code. All the rest of the lines are from existing libraries I have. And a lot of the 1600 lines are actually "stock" lines that are easy to generate, or comments. The total investment here is about 800 lines of original source code.
It reads the .cpp files, looking for the IMPLEMENT_DYNAMIC, IMPLEMENT_DYNCREATE or IMPLEMENT_SERIAL declarations. From these declarations it creates a tree diagram of the inheritance. I did not write a general-purpose C++ class map because I didn't need one, and the parsing is much more difficult, especially in the presence of macros.
My first version just parsed the application, which was useful but not especially accurate because it didn't have the full derivation.
The output if the full MFC hierarchy is not included is shown below. This does not use the option to search the MFC source.
What I added was the ability to scan the MFC source as an option. This then produced overkill, so I modified it to show only the MFC classes that lead to classes in the application. This is done by propagating an inclusion bit upwards from the leaves, then ignoring any nodes in the derivation tree that do not have a bit that indicates they are either from the original source or from the propagation algorithm.
A sample part of the output is shown below.
The dialog requires the path to a source directory. Currently only a single source path is supported. The browse button will open a standard browse-for-folder dialog.
To use the MFC source, it also requires a path to the MFC directory. Checking the MFC option enables the edit control and a browse button to search for the MFC source.
When the paths have been selected, the Go button will read all the files and generate the tree.
The button is "Expand All" and will expand the entire tree. The button is "Collapse All" and collapses the tree so only the top-level node (or nodes) are displayed.
Hovering over a class name will pop up a tooltip that gives the source file and line of the IMPLEMENT_DYNAMIC, IMPLEMENT_DYNCREATE, or IMPLEMENT_SERIAL. (Note that for CObject, which has no superclass, this information is not displayed)
This will write a text file that can be included in the source file. It uses "ASCII Art" to display the information. The file extension is .tree. A sample section of the output is shown below (the entire output is too long to include in this document).
File written Monday, March 19, 2007 0:14:19 +--CObject | +--CCmdTarget | +--CWnd | +--CDialog | | | +--CPropertyPage | | | | | +--CBlockDiagram | | | | | +--CLeaves | | | | | +--CBasicCPU0 | | | | | +--CBasicCPU10Regs | | | | | +--CBasicCPU1EAX | | | | | | | +--CBasicCPU1EAXAMD | | | | | | | | | +--CExtendedCPU1EAXAMD | | | |
This will write a graphical metafile that can be included in other forms of documentation. It uses graphics primitives to draw a .emf file. This image was created by reading the .emf file into a program and emitting a .gif file.
Prints the tree on the printer.
Exits the application.
This expands all the nodes, the same as the button.
This collapses all the nodes, the same as the button.
This selects the font used to display the tree in the Graphical Metafile.
Shows the About dialog.
The parser is incredibly trivial; it looks for the three options, IMPLEMENT_DYNAMIC, IMPLEMENT_DYNCREATE and IMPLEMENT_SERIAL, and extracts the class and its parent.
The class table is simply an array of class-parent pairs. It includes the name of the class and the source information where the declaration was found. When a parent is found, it is entered in the class table as a "forward reference" because the defining instance may not have been found yet. A class may have only one defining instance.
To simplify later display, the class table is sorted alphabetically by class name. It is then searched sequentially from beginning to end. When a node is found, its parent chain is chased upwards. If the node that was found was found in the Folder set, a "propagated bit" is set all the way back to the root of the chain. As a performance optimization, if a node is found that already has the propagated bit set, the propagation ceases because it is now redundant.
To add elements to the tree, the array is scanned from beginning to end. When a node is found that has a NULL parent, it is added to the tree at the top level. Then its child nodes are added recursively below it. Nodes that are not marked are ignored.
The tree printers are recursive printing routines that create the proper indentation marks. The text file uses "ASCII art" and the graphical representations use actual line drawing primitives; otherwise the logic is essentially the same. However, the graphical representation needs to account for page breaks when producing a printed listing, so there is special code to force the vertical connector lines to be continued properly between two pages.
There is something really odd about how CMetaFileDC is implemented. It exhibits truly weird behavior. For reasons which appear to be unfathomable, certain DC operations use the output DC, the m_hDC member of the CDCclass, but other operations, such as those that read information, work on the "attribute DC", the m_hAttribDC member. Unfortunately, some of the state that I wanted to read is the result of doing the output, but the m_hAttribDC member is NULL. So the obvious solution is to set the m_hAttribDC to the m_hDC value. But this leads to other strangeness, where certain operations will ASSERT(m_hAttribDC != m_hDC). It is not really possible to figure out how to do things like query the current position left after doing a graphic operation unless the m_hAttribDC is the same as the m_hDC. So at various times I have to manipulate the m_hAttribDC to make the program work. The suggested solution given in the discussion of CMetaFileDC does not make any sense.
The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.