Visual Studio extensibility developers know that VSPackages are load on-demand. The IDE loads a package when any of the commands, services, tool windows, document windows, designers, or whatsoever objects the package owns are about to be used. Even if our packages are not loaded, somehow they should indicate their presence: “Hey, here I am installed with Visual Studio… Just click on this button and I’ll be loaded by the shell to stand at your service”.
Generally packages indicate their presence by inserting menu and toolbar items either into one of the main menus, standard toolbars or context menus. Assemblies of packages hold the information the IDE uses to indicate their presence. The dilemma is the following:
If the package holds this information, they should be in the memory to extract the information to be displayed by the IDE. Well, this would totally prevent the frugality of the on-demand loading mechanism.
Visual Studio solves this challenge with the following technique:
- Presence information is put into a special binary resource within the assembly representing the package. This is the result of compiling the so-called command table (.vsct file of the package).
- When installing the package, Visual Studio takes this resource information out of the assembly and merges the items described there with the standard menus (of course, including toolbars and context menus) of the IDE.
This sounds simple, but in practice, this mechanism is more complex, because several other issues should also be handled:
- Removing or uninstalling packages
- Handling changes in packages
- Packages can be localized for several culture contexts
- Startup performance should not be poor
Technically the easiest solution was to merge the menu information of all packages during Visual Studio startup time. The IDE could simply load all the command table resources from package binaries and carry out the merging. Well, from user experience point of view this would not ideal. Just think about that Visual Studio ships with more than hundred packages out of the box. The merge process should scan over one hundred assemblies or native packages for command table resources. Are you sure you wanted this process to be done for every VS startup? I’d rather believe you would pray for a much faster startup.
What Visual Studio does is pre-merging the IDE and package menus. With the devenv /setup command line you could ask Visual Studio to do the whole merging for you.
How Menu Merging Works?
Related to a minor bug I changed a few emails with Paul Harrington, a lead dev from the VS platform team. As a tool for finding the bug, Paul wrote me the merging and startup mechanism in details. I used his description to discover how the little cogs work behind the scenes and here I share it with you.
The menus Visual Studio IDE is about to merge are described in the registry under the HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\<instanceRoot>\Menus key, where <instanceRoot> is the registry root of the Visual Studio instance you work with. For example, for VS 2008 it is 9.0, for VS 2010 it is 10.0_Config, for VS 2010 Experimental Instance this key is 10.0Exp_Config. The next figure illustrates the content of this registry key:
The key has entries with type of REG_SZ for each package having menu resources to be merged with the IDE. Each entry contains a name (like Menus.ctmenu) and a version number separated by a comma. When merging the menus, Visual Studio extracts the resources here, creates the merge and saves the result into a file named devenv.CTM where the extension name stands for “Commands, Toolbars and Menus”. This file serves as a “merged menu cache”. When Visual Studio starts, it checks if there is valid “merged menu cache file” or not. While a valid file is found, the IDE reads the file and uses its cached content to build up the menus. When the file is found invalid, Visual Studio creates the file again by merging the appropriate package resources again.
Checking the devenv.CTM file’s validity
During the merge process Visual Studio calculates a checksum from the content of the values under the Menus registry key. This checksum value is saved into the devenv.CTM file. At startup time the IDE looks for the devenv.CTM file in a few folders using the locale IDE of the installed VS language. For the English version this is 1033, for other languages substitute it with the appropriate ID. Folders are searched in the following order:
- Local AppData folder. Assuming your operating system is installed on drive C:, it is C:\ProgramData\Microsoft\VisualStudio\<instanceRoot>\1033.
- Per-user local (non-roaming) AppData folder. It should be something like C:\Users\<userName>\AppData\Local\Microsoft\VisualStudio\<instanceRoot>\1033.
- The folder containing devenv.exe. This is the Common7\IDE folder under your Visual Studio installation root.
The CTM file is hidden, so you must also display the hidden files if you want to see it in Windows Explorer. Because the local AppData folder and the Visual Studio installation folder require elevated permissions to write into them, there is a high likelihood that you will find this file in your per-user local AppData folder, just like in my case:
If the IDE does not find the devenv.CTM file, or find it, but the checksum stored in that file does not equal with the value calculated from the Menus key’s content, the IDE takes the merged menu file into account as invalid (or obsolete) and starts building up the new merged menu file.
The merging process
The IDE iterates through the packages registered with the Menus key and looks for the localized resources (according to Visual Studio’s installation language). Reads the resources and merges the command+toolbar+menu contributions into a master list. When all packages are merged into this list, it is written to the CTM file together with the calculated checksum. The IDE tries to write to the locations (1-3)used to search this file. Because the local AppData folder and Visual Studio installation root require elevated permissions, generally the file is written into the per-user local AppData folder.
Relation to devenv /setup
The merging process happening with devenv /setup is the same as above, but there are a few differences:
- devenv /setup always carries out merging even when the checksums match.
- devenv /setup must be run with elevated permissions, so it should always be able to write to the local AppData folder.
- devenv /setup creates the merged menu file for all installed languages.
I hope, these details will help you understand how your extensions’ commands, menus and toolbars are merged into the UI of Visual Studio and also give you starting point for troubleshooting.
Oct 13 2009, 03:53 PM