Another Enum Viewer
This article explains the usage and development of an enum viewer tool.
Usage
Download the enum viewer's .exe's into the same directory. Run enum_viewer. Open a header file. Click on the column buttons to sort.
Development
1. The problem
My day job is writing software that talks to embedded devices via Win32 communication channels (rs232, rs485, tcp/ip, etc.). In order to minimize backwards compatibility and protocol flexibility, we designed a message passing protocol using lengths and identifiers. Messages all have a unique identifier in a 16 bit
enum
value defined in a header file.
I am in the process of writing a device simulator and wanted the enum
values when looking at raw messages. None of the tools I've seen handle comments,
#defines
, enums within classes, enums with enum
assignments, etc.
The Visual C++ IDE, browser and debugger don't show enum
values (the debugger does one at a time if
typedef'
d).
2. The solution
I figured that there must be some C++ parser out there somewhere that might provide a basis for an enum viewer. A quick search on google turned up John's PCCTS-based C++ Parser Page.
Neat! A C++ parser! I downloaded and attempted to build the code but the compilation stopped with error messages regarding STL allocators. After trying to coax the parser into using
STLPort
, I gave up and used the default MSVC STL and it all compiled.
Then I turned to the docs (some of you are probably snickering right now, stop it). After looking through the docs I got a little frustrated and had a brief flirtation with regular expressions. Thinking about all ways of declaring / defining enums chased me back to the parser.
Looking at the directories again I noticed a sample application called cpp_test. I compiled and ran it against a header with a lot of enums.
The results were encouraging, all of the enum's names were displayed along with their recursive values eRED = (((10)+1)+1)
.
Digging into the code, I found the dumping functions and looked up the definitions of the various class members thanks to Joshua C. Jensen's very fine Workspace Whiz.
I only had to derive two new classes, move a function from private to protected, and I had a command line app that could turn a header into an ascii file of enums and their values.
3. Displaying the data
As cool as that was, I still wanted to sort enum names or their values. I wanted a GUI capable of browsing, sorting and displaying data in hex. Hmm... I've done quite a bit of work using ListView controls in the last couple of years and this seemed a good fit (just like everything looks like a nail when you're holding a hammer).
I generated an AppWizard MFC SDI app and started working on the parsing of comma separated value files.
I decided to make the ListView virtual for these three reasons in order:
- More flexible, all formatting is done at the time of display
- Less memory Usage
- Sort works much faster
The whole point of a virtual ListView is so the application maintains the string representations of the listview in memory. In that end, I used a two dimensional
std::vector
of CString
s to cache data from the document. I use the
WM_NOTIFY
message LVN_ODCACHEHINT
to initialize the size and content of the cache. The ListView responds to the
WM_NOTIFY
message LVN_GETDISPINFO
by handing out pointers to cached
CString
's buffers via GetBuffer()
. I could have used std::string
, but did not want to convert integers into decimal and hex using
std::stringstream
when a simple call to CString::Format
could be used. For more details, see the code in enum_view.cpp.
Having settled the view, I moved on to implementing the document.
Two important things that virtual views need are the row count for initialization and individual row access to feed the cache.
Counting Rows
Each row in the listview corresponds to a line in the parser output file. Each line looks like:
enum name,value\r\n
Therefore, the number of \n's in the document is the number of records. An easy way to do this using the C++ standard library:
size_t lineCount = 0;
while ( m_fileIn.ignore(std::numeric_limits::max(), '\n') )
++lineCount;
Where m_fileIn is any istream.
Individual Row Access
This is a no brainer for C++ iostreams. One of the nice things about using iostream
extractors is that the status of an extraction can be implied from doing a boolean test on the extraction itself. See Marshall Cline's excellent C++ FAQ for details.
m_fileIn.clear();
m_fileIn.seekg( fileOffset );
std::string strName("");
parsed = std::getline(m_fileIn, strName, ',') && m_fileIn >> value;
if (parsed)
name = strName.c_str();
On my first cut at getting the records, I seeked the file to the beginning, then counted up to the requested index. This made the view pretty slow! I sped things up considerably by making an index of file offsets and seeking to them on demand.
I then wired up the request for open file to spawn a new process of the enum parser passing it a temporary file name.
Sorting
I read this really useful tip in the C/C++ Users Journal about a generic STL index creation function. Herb Sutter did a good source code review on the code over at Guru of the Week with many improvements. I implemented a few of them and added one of my own (see the code for details). The function
create_index
takes _any_ sequence of iterators, applies a sort on them, and fills in index numbers of the sort result. The view keeps around one of these index sequences and uses it to display the data. If the current column is already sorted, then the index order is simply reversed. All thanks to the beautifully orthogonal STL.
Most Recently Used File
Since the CDocument
uses temp files, the MRU list gets all these temp file names that no longer exist. The app removes these and adds the header name instead.
That's it. Please check out the code if you have any questions. If you find any bugs, let me know too. I might even fix them. =)
As an aside, if given the choice between using an MFC or a standard C++ library method, I'll prefer the standard. My reasoning is that:
- 3rd Party documentation for the standard library is much better (I'll take Austern, Josuttis, et al. over any MFC author)
- Standard library interfaces are much more consistent and orthogonal
- More flexible platform / compiler support