The strange case of the add-in initialized twice

As I commented in my last post, I have developed my own integration test infrastructure to test my MZ-Tools add-in. As part of it, there is an add-in that is the test runner, that when loaded loads in turn the MZ-Tools add-in if not loaded, locates its friend assembly that contains the integration tests, loads it, gets the test suites, gets the test methods, shows them in a treeview and executes the ones that I select.

This worked fine if the test runner add-in was not marked to load on startup and I had to load it with the Add-in Manager. But when the test runner add-in was marked to load on startup, the MZ-Tools add-in was initialized twice, giving an exception because some code didn’t expect to run twice (duplicated key).

The code of the MZ-Tools initialization is similar to the one that I wrote in my article HOWTO: Use correctly the OnConnection method of a Visual Studio add-in and that I constantly recommend in the MSDN VSX Forums:

AddIn _objAddIn;
EnvDTE.DTE _objDTE;

void IDTExtensibility2.OnConnection(object objApplication, ext_ConnectMode eConnectMode, object objAddInInst, ref System.Array r_objCustom)
{
   _objDTE = (EnvDTE.DTE)objApplication;
   _objAddIn = (AddIn)objAddInInst;

   switch (eConnectMode)
   {
      case ext_ConnectMode.ext_cm_Startup:

         // IDTExtensibility2.OnStartupComplete will be called
         break;

      case ext_ConnectMode.ext_cm_AfterStartup:

         Initialize();
         break;
   }
}

void IDTExtensibility2.OnStartupComplete(ref System.Array r_objCustom)
{
   Initialize();
}

private void Initialize()
{
   ...
}

This pattern assumes that:

1) If an add-in is loaded on startup:
  • The OnConnection method will be called with the ext_ConnectMode.ext_cm_Startup flag.
  • The OnStartupComplete method will be called later TOO, when the Visual Studio IDE has completed its initialization.

2) If an add-in is loaded through the Add-In Manager:
  • The OnConnection method will be called with the ext_ConnectMode.ext_cm_AfterStartup flag.
  • The OnStartupComplete method will NOT be called (because the Visual Studio IDE was already initialized when you used the Add-In Manager to load the add-in).

But in my case, the Initialize method was being called twice. How was that?

It happens that there is a subtle behavior here: when an add-in marked to load on startup loads in turn another add-in (using EnvDTE.AddIn.Connect = true), in the second add-in the OnConnection method is called with the ext_ConnectMode.ext_cm_AfterStartup flag, AND the OnStartupComplete is called too!!! (because the Visual Studio IDE was not initialized when the first add-in was loaded on startup). So, the Initialize method is called twice.

I was about to report this as a bug, but I have thought that maybe the behavior is correct after all, that is, when VS has finished its initialization it calls OnStartupComplete for all add-ins that are loaded in that moment, independently of whether they were marked to load on startup or they were loaded by another add-in marked to load on startup. And what is really misleading is the MSDN documentation about the OnStartupComplete method:

(OnStartupComplete) “Occurs whenever an add-in, which is set to load when Visual Studio starts, loads.”

That implies that if add-in is not set to load on startup, its OnStartupComplete method will not be called.

The Remarks section is correct, though, since it does not relate the OnStartupComplete call to whether the add-in was set to load on startup or not:

“On occasion, OnConnection does not occur correctly, such as when an add-in is loaded, but a component required by an add-in has not yet loaded. This is unusually due to the fact that Visual Studio has not yet started completely. Using OnStartupComplete guarantees that the Visual Studio integrated development environment (IDE) has completed the startup process.”

As you realize, this is a subtlety that your add-in won’t experience unless is loaded by another add-in when Visual Studio is started.