Search code examples
c#visual-studiovisual-studio-2019vsix

Difference between the different Solution interfaces


When writing an VSIX and you want to access information about the given solution you can simply do DTE2.Solution which will return Solution, so far so good. When looking at the MSDN I can see that there exist multiple solution interfaces: Solution, Solution2, Solution3 and Solution4.

I noticed that the VSIX SDK rather often does this, for whatever reason, in order to offer different functionality. In this case I can't really spot any big difference and I am not really sure when I should use which. Should you always go for Solution4 since it implements all the predecessors?


Solution

  • ... for whatever reason, in order to offer different functionality

    These are COM interfaces so they're subject to COM rules. Once an interface is published, it is immutable, so adding functionality is done by defining a new interface that inherits from the old.

    Numeric suffixes were the convention Microsoft used for versioning COM interfaces. .NET guidelines for interfaces advise against this but, for consistency, the pattern continues in Visual Studio.

    Querying a COM interface involves reference counting. That means a call to QueryInterface to get a pointer to the desired interface, and ultimately a call to Release to tell the object you no longer need the reference. The object itself is responsible for its lifetime. The constructor (called by the object's class factory) starts its reference count at 1, and Release deallocates the memory (deletes itself) when the reference count hits 0.

    Note that "reference" in this context is not the same as a .NET reference. It's a counted copy of a pointer to one of the object's interfaces.

    In the early days, it took a bit of work to make sure you handled the reference correctly. If you can query a newer interface with the combined functionality, that was less work than querying both interfaces separately and ensuring that both were released properly. Inheriting an interface made things easier by reducing reference management.

    Now, we have smart pointers, like CComPtr/_com_ptr_t, that can handle those details for you. In the managed world, Runtime-Callable Wrappers (RCW) count that among their responsibilities.

    In .NET, it's just as easy to create a new interface as it is to inherit an existing one, and just as easy to use the interface in either case. It's just a matter of a reference cast and sometimes that happens implicitly.

    C# 8 adds default implementations but that's a .NET-specific feature. Remember, Visual Studio is still using COM interfaces at its core. Default implementations violate the COM rules.

    Should you always go for Solution4 since it implements all the predecessors?

    That depends on what you're targeting. As a general rule, use the minimal interface version that has the members you need. If you need members from Solution and Solution3, use Solution3, but not Solution4.

    On the other hand, if you know you're targeting at least a version of Visual Studio that implements Solution4, there's no reason you couldn't use Solution4. Once you're certain that you're not accidentally preventing your extension from running in the Visual Studio versions you want to target, it falls to your preference.