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.

Long time without blogging

I have been long time without blogging, and the reason is that I have been quite busy doing the following:

– I have migrated the whole code of my MZ-Tools 7.0 add-in from VB.NET to C#. After 10 years programming in VB.NET, I decided to switch to C# and the best way is to use it everyday, so I had to migrate the product. The migration was successful and build 7.0.0.103 released on December 1 was C#-based. Only a couple of bugs were introduced that were not detected by the automated integration tests. FWIW, I used Instant C# from Tangible Software Solutions.

– I have enforced the Code Analysis feature of Visual Studio on the MZ-Tools code base with All Microsoft Rules, and after lots of fixes I was able to pass all with some custom suppressions and four of them disabled: CA1031 Do not catch general exception types, CA1502 Avoid excessive complexity, CA1506 Avoid excessive class coupling and CA5122 P/Invoke declarations should not be safe-critical. If you have tried to enforce them on a large code base you know how time-consuming is that.

– I have done massive architectural changes in the code base of MZ-Tools for Visual Studio to prepare a new unified version 8.0, .NET-based, that will support Visual Studio (VB.NET, C#), Visual Basic 6.0 (“Classic”), VBA editor of Office 32-bit and VBA editor of Office 64-bit. That means to encapsulate the automation models of VS and VB “classic”. I already have the user interface, options, setup and unit-test/integration-test subsystems. It “only” remains the features, but it will take me months yet :-). I will blog about this in the next months when I am closer to the release.

– I have created an integration test runner that runs in the IDE where MZ-Tools is loaded, rather than in the IDE where the MZ-Tools source code project is loaded. While the Visual Studio SDK provides a remote MS-Test-based host adapter for this purpose, I tried it two years ago with disappointing results, so I created my own integration testing infrastructure in MZ-Tools. But the MZ-Tools add-in, its integration tests and the test-runner were in the same assembly (using a special configuration). Now I have isolated them so I have the add-in, the integration tests and the test-runner in three separate assemblies. I hope to release the test runner in CodePlex or similar some day.

– I migrated to Visual Studio 2012 (I am almost used to the new UI style) and I am planning to adopt TFS (I am finishing the simultaneous reading of Professional Team Foundation Server 2012 and Testing for Continuous Delivery with Visual Studio 2012). In the past I used Perforce, but after a failed restore after a crash (likely my fault) I didn’t use source control for some time and I want to adopt TFS now.

And I was almost three weeks on vacation during Christmas, resting and watching lots of TV series 🙂