Properties: How they work, why they exist

From talking with people, it appears that one area of confusion is over the use of the Properties object. How should you use it, how do the contents of this collection get generated, and why does it exist?


To start off, there is a Properties object available in a few places. The two most common places are from DTE.Properties and from Project.Properties. DTE.Properties is indexed, meaning that there are a number of different Properties objects that can be found. When Visual Studio starts, it goes to the registry key and enumerates all the property category keys from HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\VerMaj.VerMin\AutomationProperties. The categories (Environment, FontsAndColors, Projects, etc.) are used for the first argument to DTE.Properties. Under the top level key, you can then see a list of sub-categories which is passed as the second argument to DTE.Properties. For example, the Environment category has the subcategories Documents, General, Keyboard, TaskList, etc. If you are using Project.Properties, then the contents of the Properties object is hard coded and not dynamic (except if the object has an extender), the project decides what is returned. For languages such as C#, VB, and J#, the object that is wrapped is in the VSLangProj series of libraries (VSLangProj, VSLangProj2, VSLangProj80, depending on the version you are using), and the type is ProjectProperties.


How is a Properties collection generated? When a Properties collection is requested by the user, the method IVsExtensibility::get_Properties is called, passing in an IDispatch object. get_Properties will get the ITypeInfo for this IDispatch object then start walking all the properties available that are not marked hidden or restricted. Then, one Property object is generated for each property (with the get/set operations coalesced into one) on the IDispatch object. Something new in VS 2005 is the aggregation of extenders onto a Properties collection. An extender is a way of attaching your own object onto another object for display in the Properties window. VS 2005 will now look for any extenders on an IDispatch property object and also add those into the Properties collection. These new extender properties have the name Extender.PropertyName name in the properties collection. When you call Properties.Item(SomeName), the GetIDsOfNames of the ITypeInfo method is called, passing in SomeName as the ID to retrieve, then the Property object for that ID is returned. Calling Property.Value will, behind the scenes, call IDispatch.Invoke passing in the ID for the property, the value contained in a VARIANT (or System.Object for you .NET users) is also passed to the Invoke call if setting the value, or the VARIANT is returned if the get version of the property is called.


Where is the type info for these property IDispatch objects? In VS 2002/2003, they live in a few different places. The property objects for objects found off of DTE.Properties(…, …) can be found in EnvDTE – but you may need to look a bit to find them. All the types are marked hidden and restricted, as they are only to be used by code internal to VS. Most also have the format _CategoryNameSubCategoryName, where, as you would expect CategoryName is the category name of the property, and SubCategoryName is the subcategory name. Examples are _EnvironmentGeneral and _EnvironmentDocuments. Some of the text editor properties follow the same format, but rather than starting with an underscore, they start with IVs. Examples: IVsTextEditPerLanguage and IVsTextEditGeneral.


So why do we jump through all these hoops to get this to work? We try to keep the object model static between different versions, that means no big changes that will break code. But the tools options dialog box, the UI that properties wraps, changes a lot between different versions. If we just returned an object that you could program, then the next version your code would most likely not work because the interface shape changed, causing a crash. By wrapping this object with a Property object, if you try to get or set a no longer supported property you will get an exception or bad HRESULT returned to you, but you will not crash (unless you do not catch the returned error).


Application compatibility also caused another problem. When we change an object that is wrapped in a Properties collection, we also need to change the type library / PIA that contains the types. Changing a type library is not good and can cause some compatibility problems, but changing a PIA is downright evil. Just changing a PIA will cause anything that uses that type to no longer load (unless you use binding redirects, which is also a pain). So for VS 2005 we left all the property objects in the EnvDTE library, however we created a new typelibary, dteproperties.tlb, that contains all the new types. Since you really have no need to reference this library from your code, we can now change the property objects over time without causing any side-effects to your apps.