To Microsoft: Sporadic COM exceptions adding/removing commandbars and buttons from Visual Studio add-ins
Hi Microsoft people,
My add-in adds buttons to built-in commandbars of Visual Studio and also it creates several commandbars with lots of buttons. It is my painful experience (from user bug reports) that the Commandbars of Visual Studio .NET 2002/2003 and VS 2005 are very fragile and cause sporadic COM exceptions in non-reproducible circumstances. When a user reports this, my first question is: "Is it reproducible?" and the answer is always "No, the add-in loaded/unloaded fine the next time". So, I am certain that my add-in is not buggy. I have done a lot of effort to identify some reproducible cases and I have reported them though MSDN Feedback Center such as:
but there is still something very wrong inside the Office command bars that Visual Studio uses that causes COM Exceptions from time to time for no apparent reason. Needless to say that the COMException message is useless and that this causes in my users the impression that my add-in is buggy, although I take care to move that perception towards Visual Studio once they notice that the problem is not reproducible.
I can send you the exceptions, HRESULTs and methods that fail if that helps, but I would recommend a couple of things:
- Review carefully the source code of the Commandbars, CommandBarButton, etc. methods to identify all the COM exceptions that can be thrown and why. At the very least, provide some useful exception information to report back to you.
- Test Visual Studio more extensively with heavy add-ins loaded. Some of the reports above clearly denote that neither the developer nor the testers tested if with add-ins loaded.
Let me know your thoughts about this.
Visual Basic MVP
Looking at your second example, we are working as we should (although we could give a little bit more error information). The problem is in how you are adding your controls. You are first creating a command by calling Commands.AddNamedCommand. Next, you are adding a UI for the command to a command bar using the Command.AddControl method. This is all correct.
The problem is in how you are trying to delete the command. The method CommandBarControl.Delete was designed to delete non-permanent commands, meaning ones that are added through Microsoft.VisualStudio.CommandBars.CommandBarControls.Add, not through AddControl. Mixing the two approaches to command bar manipulation can cause problems, such as the one that you are seeing. With the old model you can enumerate, inspect, and manipulate properties on controls created using the new model, but you should not try to remove commands using the old model. If you wish to hide the command during shutdown, you can call Command.Delete (not advised for reasons that we have discussed before), or modify the call to AddNamedCommand to hide the command, and selectively enable it using the QueryStatus method.
The same is true for part of the first question. You are creating a command bar using Commands.AddCommandBar to add a command bar, but using Microsoft.VisualStudio.CommandBars.CommandBar.Delete to remove it when the correct way is to use Commands.RemoveCommandBar.
As for the problems with import/export settings, this was a design decision made because we did not know the best way to make this work (but have since found a solution). When you export your settings, we exclude Add-in and macro commands from the generated vssettings file because chances are those commands will not be available on a computer that you reimport the settings to (you may have one or two Add-ins that you carry around, but if you develop Add-ins, you will have a lot of test Add-ins that you threw away). Importing command settings just overwrites your old data, not a merge. The real fix here is to call the same code used for /resetaddin, but we shipped VS by the time that we saw this as a fix.
For the last item, this is being caused because of how menu merging is performed. When an OLE document is active and it uses menu merging, the menu command bar is destroyed and replaced with a standard Win32 menu. When switching focus away from the document, the Win32 menu is replaced with the command bar. The problem is that Win32 menus do not have a programmable interface, so adding a new item to the menu fails. You can still get to the the command bar using the Item method because we work off of the data rather than the UI, but adding new controls to it fails because the command bar to add the UI to fails because there is no UI. We can look to see what we can do here and see if we can fix anything for the next version.
> The problem is in how you are trying to delete the command.
> The method CommandBarControl.Delete was designed to
> delete non-permanent commands, meaning ones that are added through
> Microsoft.VisualStudio.CommandBars.CommandBarControls.Add, not through
> Mixing the two approaches to command bar manipulation can cause problems,
> such as the one that you are seeing. With the old model you can enumerate,
> inspect, and manipulate properties on controls created using the new model, but
> you should not try to remove commands using the old model.
If the AddControl method returns a CommandBarControl, I should be able to use all its properties or methods. At the end of the day, you can add an internal flag to it to know how it was created (AddControl / Controls.Add) and put code for each case in the Delete method, for example. If the existence of two models is quite unfortunate the fact that you can not mix them is even more unfortunate, it seems a mine field, you touch the wrong property or method and boom!
> or modify the call to AddNamedCommand to hide the command,
> and selectively enable it using the QueryStatus method.
And this, although it may work, is so obscure that we are going to document well in the MSDN help, blogs, KB articles, etc... I really think that you should ease all this stuff.
> The same is true for part of the first question. You are creating a command bar
> using Commands.AddCommandBar to add a command bar
I don´t think so, which "first question" are you referring to? The Import/Export bug report uses CommandBars.Add and not Commands.AddCommandBar, so calling CommandBar.Delete is correct, but it is crashing, because the Import/Export wizard deleted a commandbar (permanent or temporary) belonging to an add-in ...
> The problem is that Win32 menus do not have a programmable interface
Then at the very least there should be a way for an add-in to detect that situation
Anyway, the three documented cases that I exposed are reproducible cases, and either they are bugs, they are "by design" or the correct way of doing it is other. But my main problem is that there are many cases where I get non reproducible COMExceptions. If I am doing something wrong, it should crash always, and certainly I had sufferered and learned from those crashes and after fixing the code it works fine, but there are still cases that cause COMException for no aparent reason, and the next time that the user unload/reload the add-in it works fine without changing anything else. Those are the ones to be worried about... reviewing your source code of the commandbars and enhancing the error information we could report back to you useful info to know what caused the exception...
Craig, I have updated the following bug report with a new problem that I found trying to use a workaround. Could you take a look?
COMException removing commandbar unloading add-in after Import/Export settings
Thanks for your attention. Some comments:
- About the Import/Export confirmed bug, let me know any progress about it. You can use the MSDN Product Feedback report instead of this forum.
- About the problem when an Office designer is active, I realize that it can´t be solved. I have a workaround in my code that detects the situation, forces a change of window focus to get the original IDE toolbars, add/remove the needed UI and passes the focus again to the Office designer. It is working fine (kind of) but it is difficult and error prone to detect if an Office designer is active. Maybe you could add some property to the Orcas automation model to ease this part, just a suggestion.
- About the problem "COMException removing button from Solution Explorer commandbar", the bug report that I opened in the MSDN Product Feedback center:
was closed by some MS engineer acknowledging the bug and submitting a fix. This seems to contradict your "by design" answer. Could you clarify it?
- Also, maybe it is not the official way of doing things and maybe I have been wrong all these years, but calling CommandBarButton.Delete on a button created by Command.AddControl has worked fine all the time for me and many others (I think, at least). We didn´t buy the concept of permanent UI (that it is there even if the add-in is unloaded through the add-in manager) and we are using that method extensively in our add-ins.