Introduction
Most of my application development these days is in C#. Over the past two years, I have grown accustomed to the C# XML comments built into the C# editor. As we all know by now, you can edit the project properties of your C# application and define an XML documentation file. With that processed XML file and the compiled assembly, NDoc can automatically build a Windows help file for your software.
There are days, though, when I have to leave the friendly confines of C# and drop down to C++. When this happens, I am usually writing a managed C++ class that hides unmanaged functionality (for example, serial port access). At that point, though, we have lost our ability to use XML commenting of our source code. Without XML commenting and the processing of those XML comments, we cannot use NDoc for that subsystem. That is a real problem, given that 90% of my projects are C# and documented in NDoc!
A while back, I wrote a Visual Studio Add-in (in C#) that handles XML commenting for managed C++ applications. It allows you to type XML comments. When the assembly is compiled, it processes the XML comments and builds a processed XML file that can be used as input to NDoc. This way, I can consistently comment all of my source code!
Writing Add-Ins
Support for add-ins in Visual Studio exists in the form of an "extensibility" project called "Visual Studio .NET Add-in". I will not cover the nuances of creating add-ins. If you want a good book on the subject, refer to the book "Developing Visual Studio .NET Macros and Add-Ins" by Jeff Cogswell. It is one of the few references I found.
Once you create an add-in project, you can then create event handlers for the specific events that are relevant for your add-in. There is an extensive object model describing the layout of the Visual Studio environment, solutions, and projects.
Two Areas To Address
When designing this add-in, there were two separate areas of concern. The first is to provide support for entering XML comments into source code. As we all know, when you start typing '///' in the C# editor, it automatically will expand if it precedes a method or property definition. I wanted to try to maintain a similar user experience.
The second area to address is the processing of those XML comments in order to build the XML comment files that can be used by NDoc.
Entering XML Comments Into Source Code
The first decision I made was that XML comments would be added to the header file. That is where all methods and properties will be defined, so naturally it is a logical place for the user to enter the XML comments. When I created the project, I was hoping to find an event that would fire each time the user pressed a key in the editor. However, that level of notification does not exist. I quickly discovered that the only editor event that came close was a line changed event from the TextEditorEvents
object. This event gets fired every time the line number changes, including when the enter key is pressed.
Practically, this means that we cannot duplicate the exact behavior of the C# comment completion feature. The best that could be done is to make a check for '///' when a line changes. Hence, if you type '///' then press the enter key, the add-in will determine if XML comments are appropriate. If so, then it will expand the section to look like the familiar:
CSerialPort(void);
From there, you can enter a summary comment.
As noted above, when you enter a new comment section, the code in the Line Changed event will scan the editor to see if the comment is before a function or a variable definition. If it determines the target is a function, it will attempt to decode the parameters of the function and build the appropriate XML comment structure. For example:
bool Open(int port);
If you are in the middle of an XML comment section and you press Enter, it will recognize your position and automatically add '///' to the start of the new line and space it with the other comment tags in the section. For example, in the example above, if the cursor was at the end of the 'param name = "port"' line and you pressed Enter, the comment section would look like this...
bool Open(int port);
Processing XML Comments to make the XML File
After the XML comments are entered into the header file, they need to be processed to create the XML comment file. To do that, I create an event handler to monitor the Build Done event for the project. Whenever a project is built, that event will fire and the add-in is notified. It will check to make sure that the XML comment file should be built. It only builds the comment file for managed C++ projects that have been successfully built.
The building of the XML file is fairly straightforward. When the project is built, the compiler creates a data structure that describes all the entities associated with the code. Every property, method, enumeration, namespace, etc. is represented as a CodeElement
. We can examine the CodeElement
object for the project after it is built, to get a list of all entities in the project. We can iterate through the object list and examine the Kind each element represents. Based on the type, we create nodes in the XML document object that follows the format for the XML commenting scheme.
The code is not perfect
I developed this code rather quickly. Like most of us, I do not have time to make it perfect. It does what I need it to do.
There was one area in particular I wanted to address, but was unsuccessful. The way the add-in works now, the XML comment file will always be built if you are compiling a managed C++ application. The file it creates is based on the project file name. The file is always placed in the active configuration directory. For example, if the project is called 'MyProj' and you are building a Release build, the XML file will always be "Release/MyProj.xml". What I tried to do was build a project extender property that would allow you to specify the XML file name and location. I was unsuccessful in getting that done. If anyone has suggestions, let me know.
Limitations
The add-in assumes that attribute definitions are defined immediately preceding a function definition on the lines above the function. When processing the code, the add-in examines each line using a simple Regex.IsMatch
function. When looking for attributes, it uses "\\[(.)*\\]".
History
- 5/14/2004 - Corrections for bug when processing function parameters.
- 5/20/2004 - Added exception handling and stack trace when failure occurs.
- 5/26/2004 - Handles interface documentation. Fixed misc. bugs in decoding parameter text.
- 8/09/2004 - Attributes are now handled. See 'Limitations' section above for more details.
- 9/22/2004 - Updated based on user feedback. Should fix problems with enumerations and parameters. Many thanks to M Ward and FredericM102LI90.
- 11/09/2004 - Fixed exception being thrown when no .h file exists. Thanks to barrd for pointing this one out.