This MSVS Add-in allows to select multiple files contained in loaded solution and pass them to Doxygen. Also it gives availability to choose the name of the project and the destination folder. All other Doxygen options can be set using Doxywizard executed from the add-in. I tried to do it as simply as possible for future extension which will be done next time.
- Microsoft Visual Studio 2005
- Doxygen. Download from http://www.stack.nl/~dimitri/doxygen/download.html#latestsrc
- Boost(I used boost.1.38.0). Download from http://www.boost.org/
- WTL 80. Download from http://www.microsoft.com/downloads/details.aspx?familyid=E5BA5BA4-6E6B-462A-B24C-61115E846F0C&displaylang=en
Doxygen Add-in can be run from Tools menu of MSVS IDE.
This tab allows to select files for document generation:
This is used for Doxygen raw tuning:
“Doxygen executable” & “Doxywizard executable”
They are searched when add-in starts. But I left an ability to choose them manually if you need it.
“Name” is used for document generation and can be changed in Doxywizard.
“Doxygen configuration file”
When running Doxywizard, you can fine tune the options of generated documentation and save various configuration files for different purposes. Later, you can choose the configuration that you want and run Doxygen.
“Output directory” is needed to tell Doxygen where to put the documentation. It can also be changed in Doxywizard mode.
MSVS Add-in Architecture
First of all, I will try to explain (and to understand myself) some important moments of the MSVS Add-in architecture.
Let’s take a look at this diagram:
(*)This happens only if you use
CommandPreload flag set to ‘1’ in your registration script(Addin.rgs). It indicates that your add-in must be preloaded to setup UI. After that, the add-in will be unloaded.
Important: Remember that all initialized class members and local objects will be destroyed after UI setup completes.
The first time the object <
CConnect> is created in order to give availability to setup UI, what can be done in
OnConnect( ConnectMode == ext_cm_UISetup ). At this point, we add our control to menu bar (this can also be done by “Visual Studio Add-in Wizard”). The command is created using
AddNamedCommand2 method of
EnvDTE80::Commands2 interface. It is senseless to store the created command because it is created only once at the first run and saved by the MSVS IDE enviroment. Later, I will show how to get it in order to remove when we uncheck availability of our add-in in “Add-in Manager”.
After that, we receive
OnDisconnection( RemoveMode == ext_dm_UISetupComplete ) which tells us that “The add-in was unloaded after the user interface was set up”(MSDN).
And at last, our object will be destroyed. Call of
FinalRelease() and subsequent call of object’s destructor tells us about it.
After UI was set up, the add-in is loaded for the second (if
CommandPreload is set) time(the new calls of constructror and
FinalConstruct() indicates it).
Now we will receive
OnConnect (ConnectMode == ext_cm_CommandLine ). At this point, our add-in was loaded by the MSVS eviroment.
The next point is
OnAddInsUpdate() which “Occurs whenever an add-in is loaded or unloaded from the Visual Studio integrated development environment (IDE)”(MSDN). Its type(loaded/unloaded) we can detect by analyzing parameter
ConnectMode::ext_ConnectMode in the last call of
And the final point is
OnStartupComplete. It “Occurs whenever an add-in, which is set to load when Visual Studio starts, loads”.
Check/Uncheck Availability in “Add-in Manager” Notification
When we uncheck the add-in in “Add-in Manager”, we receive
OnDisconnect( RemoveMode == ext_dm_UserClosed ). At this point, we can remove our item from menu bar.
If we use
CommandPreload flag set to ‘1’ (see “Preload Addin”), then we have no variable pointing to our command and we need to get it manually. This is done in such a way:
CComQIPtr< EnvDTE::Command > dx_cmd;
IfFailGoCheck( pCommands->Item( CComVariant
( "DoxygenAddin.Connect.DoxygenAddin" ), 0, &dx_cmd ), dx_cmd );
DoxygenAddin.Connect.DoxygenAddin " is a full name of the command. It consists of 2 parts: the full name of the class “
DoxygenAddin.Connect ” and name of the command “
DoxygenAddin ”. The method
EnvDTE::Command removes it from the menu bar.
If our command was created not in
OnConnection( ConnectMode == ext_cm_UISetup ), then we can remove it explicitly calling
EnvDTE::Command::Delete() from created
When we again check it,
OnConnection( ConnectMode == ext_cm_AfterStartup ) is called. All we need to do is to re-add our control menu button using
AddNamedCommand2 method of
EnvDTE80::Commands2 interface as it was done earlier.
Unfortunately, it works correctly only when any solution loaded. When I remove button without loaded solution, I failed to add it again, :(. Why is it so – I don’t know. If anyone can explain this – I will be glad :).
Add-in Availability (enable/disable)
Another point is that I don’t want to execute my add-in when no solution is present.
CSolutionEventsSink class was written to prevent it. It inherits
__uuidof(EnvDTE::_dispSolutionEvents) and receives “
Opened” and “
BeforeClosing” solution events.
SINK_ENTRY_EX(1, __uuidof(EnvDTE::_dispSolutionEvents), 1, Opened)
SINK_ENTRY_EX(1, __uuidof(EnvDTE::_dispSolutionEvents), 2, BeforeClosing)
The code above easily solves this problem but I was interested to dig it deeper. I was interested in how to receive all dispids, function names and other information on them in the class which events I want to handle at run-time in order to perform dynamic advise.
_GetSinkMap() static method and create
static array of
_ATL_EVENT_ENTRY entries which contain information needed to associate events by their dispids with user callbacks.
Our sink class derived from
IDispEventImpl and its parent
static object of type
ITypeInfo from it helps us to receive all type information of
EnvDTE::_dispSolutionEvents. Now we can create an array of
_ATL_EVENT_ENTRY entries at run-time.
After advising on interested events of
EnvDTE::SolutionEvents object, all I have to do is to check the current state of solution in
CConnect::QueryStatus and to make the menu item enabled or disabled.
CConnect::CheckHasSolution() helps to determine if the add-in was checked in “Add-in Manager” when the solution was already present. It is simply done by analyzing the
EnvDTE::_Solution item count property (
EnvDTE::_Solution object presents always but it may or may not contain child items).
Take a look at the declaration of the class
class CSolutionEventsSink : public IDispEventImpl< 1, CSolutionEventsSink
, &__uuidof( EnvDTE::_dispSolutionEvents ), &EnvDTE::LIBID_EnvDTE, 8, 0 >
8 - is the major version of the type library. As it was built under MSVS 8.0, this number indicates its major version. In other versions of MSVS IDE, it will vary.
Acquisition of Solution Items
The acquisition of solution items is rather simple. We can obtain
EnvDTE::_Solution object from
EnvDTE80::DTE2. Then get the number of projects in it(
EnvDTE::_Solution::get_Count()) and all
EnvDTE::Project objects. Just the same, you can obtain
The only interesting thing is to identify whether
EnvDTE::ProjectItems points to a file or not. It is done by analyzing its
EnvDTE::vsProjectItemKindPhysicalFile indicates it.
Important: If you load a file to the empty IDE, it also creates a solution(
EnvDTE::Project) and project items(
EnvDTE::ProjectItems) (not shown in “Solution Explorer”) so you can easily get it.
Out of Scope
To view and select files for document generation, I use class
CCBTreeViewCtrl which extends
It adds 3 new abilities to its parent:
- Selection/deselection of the whole brunch via its parent
- Grayscale parents when child items have different states
ParseTree() which helps to process all children from the specified item
It does not seem simple for understanding and usage but when you achieve what you wanted, it looks amazing, :).
Lightweight and intuitive library for XML-parsing.