How to: Enable Non-Destructive Debugging for Add-Ins

The following example shows an implementation of Visual Studio Tools for Applications IDE integration and basic non-destructive debugging that opens a new instance of the host application. It is based on the ShapeApp sample application. The concepts behind this example are described in detail in Add-In Debugging.

There is another example that demonstrates Visual Studio Tools for Applications IDE integration and advanced non-destructive debugging in a running instance of the host application. For more information, see How to: Build and Run the ShapeAppMacroRecordingCSharp Sample.

Example

using System;
using System.Text;
using System.Globalization;
using System.Runtime.Remoting;
using Microsoft.VisualStudio.Tools.Applications;
using Microsoft.VisualStudio.Tools.Applications.DesignTime;
using Microsoft.VisualStudio.Tools.Applications.Runtime;
using Microsoft.VisualStudio.Tools.Applications.Hosting;
using System.AddIn;
using System.AddIn.Hosting;
using System.ComponentModel.Design;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.ComTypes;
using System.Collections;
using VSTADTEProvider.Interop;
using System.Threading;

namespace AIMExtension
{
    [System.Runtime.InteropServices.Guid("80A50594-7B6A-4840-B055-1322E487B2B0")]
    [System.Runtime.InteropServices.ComVisible(true)]
    public class AIMExtension : System.MarshalByRefObject,
                                ShapeApp.IExtension,
                                IExternalDebugHost

    {
        #region Constructors, Destructors

        public AIMExtension()
        {
            addInList = new List<IEntryPoint>();
        }

        // Define constructors and destructors.
        #endregion 

        #region MarshalByRefObject

        public override object InitializeLifetimeService()
        {
            // Allow remoting from Visual Studio.
            return null;        }

        #endregion MarshalByRefObject

        #region IExtension Members

        void ShapeApp.IExtension.Connect(
            ShapeApp.Application pApplication)
        {

            // Set the ShapeApp instance.
            this.shapeappApp = pApplication;

            // Create the service provider. 
            InstantiateServiceProvider();

            string hostDebugReadyEventName = "";
            string hostDebugUri = "";

            // Load the add-in into an external process.
            // Decide whether this is a non-destructive debugging case.
            for (int i = 1; i < args.Length; ++i)
            {
                if (args[i].StartsWith(exAddInPrefix))
                {
                    string assemblyName = args[i].Remove(0, 
                        exAddInPrefix.Length);
                    string[] addInInfoPartial = 
                        assemblyName.Split(',');
                    LoadAddInFromDirectory(serviceProvider,
                        addInInfoPartial[0],
                        addInInfoPartial[1]);
                }
                else if (args[i].StartsWith(addInPathPrefix))
                {
                    this.addInPath = args[i].Remove(0, 
                        addInPathPrefix.Length);
                }
                else if (args[i].StartsWith(vstaHostDebugReadyPrefix))
                {
                    hostDebugReadyEventName = args[i].Remove(0, 
                        vstaHostDebugReadyPrefix.Length);
                }
                else if (args[i].StartsWith(vstaHostDebugUriPrefix))
                {
                    hostDebugUri = args[i].Remove(0, 
                        vstaHostDebugUriPrefix.Length);
                }
            }
            // Enable non-destructive debugging.
            if (!((String.IsNullOrEmpty(hostDebugUri) || 
                String.IsNullOrEmpty(hostDebugReadyEventName))))
            {
                isNDD = true;
                ExternalDebugging.RegisterExternalDebugHost(
                    (IExternalDebugHost)this, 
                    new Uri(hostDebugUri));
            }
            EventWaitHandle readyEvent = new EventWaitHandle(false, 
                EventResetMode.ManualReset, 
                hostDebugReadyEventName);
            readyEvent.Set();
        }

        void ShapeApp.IExtension.Disconnect()
        {
            if (addInProcess != null)
            {
                foreach (IEntryPoint ep in this.addInList)
                {
                    ep.OnShutdown();
                }
                addInList.Clear();
                addInProcess.Shutdown(); 
                addInProcess = null;
            }
        }

        // IExtension members.
        #endregion 
        #region IExternalDebugHost

        public int OnBeforeDebugStarting()
        {
            CreateAddInProcess();
            int addinProcessID = addInProcess.ProcessId;
            isDebugging = true;
            return addinProcessID;
        }

        public void OnDebugStarting()
        {
            LoadAddInFromDirectory(this.serviceProvider, 
                this.addInPath);
        }

        public void OnDebugStopping()
        {
            if (isDebugging)
            {
                StopDebugging();
                isDebugging = false;
            }
        }
        #endregion //IExternalDebugHost

        #region Private Methods

        private void InstantiateServiceProvider()
        {
            itemProvider = new HostItemProvider(shapeappApp);
            typeMapProvider = new HostTypeMapProvider();
            ServiceContainer container = new ServiceContainer();
            container.AddService(typeof(IHostItemProvider), 
                itemProvider);
            container.AddService(typeof(ITypeMapProvider), 
                typeMapProvider);
            serviceProvider = container;
        }

        private void AddInProcessExiting(object sender, 
            System.ComponentModel.CancelEventArgs args)
        {
            if (isNDD)
            {
                // Clean up before shutting down.
                System.Environment.Exit(0);
            }
        }

        private AddInToken CustomFindAddIn(string addInPath,string className)
        {
            Collection<AddInToken> token = AddInStore.FindAddIn(typeof(IEntryPoint), AddInStoreExtensions.DefaultPipelinePath, addInPath,className);
            return token[0];
            
        }

        private AddInToken CustomFindAddIn(string addInPath)
        {
            if (!System.IO.File.Exists(addInPath))
                throw new ArgumentException();
            string addInDir = System.IO.Path.GetDirectoryName(addInPath);
            string addInRoot = null;
            addInRoot = System.IO.Path.Combine(addInDir, "..");
            AddInStore.UpdateAddIns(addInRoot);
            Collection<AddInToken> tokens = AddInStore.FindAddIns(typeof(IEntryPoint), AddInStoreExtensions.DefaultPipelinePath, addInRoot);

            Collection<AddInToken> addinTokens = null;

            foreach (AddInToken token in tokens)
            {
                
                    addinTokens = AddInStore.FindAddIn(typeof(IEntryPoint), AddInStoreExtensions.DefaultPipelinePath, addInPath, token.AddInFullName);
                    if (addinTokens.Count > 0)
                        return addinTokens[0];
            }
            return null;
        }

        private void CreateAddInProcess()
        {
            // Create the process and start it.
            this.addInProcess = new AddInProcess();
            this.addInProcess.Start();
            // Create the event handlers.
            addInProcess.ShuttingDown += new EventHandler<System.ComponentModel.CancelEventArgs>(AddInProcessExiting);
        }

        private void LoadAddInFromDirectory(IServiceProvider 
            serviceProvider, string addInPath,string className)
        {
            AddInToken addinToken = CustomFindAddIn(addInPath, 
                className);
            LoadToken(addinToken);
        }

        private void LoadAddInFromDirectory(IServiceProvider 
            serviceProvider, string addInPath)
        {           
            AddInToken addinToken = CustomFindAddIn(addInPath);
            LoadToken(addinToken);
        }

        private void LoadToken(AddInToken addinToken)
        {
            IEntryPoint addin = null;
            if (this.addInProcess == null)
            {
                CreateAddInProcess();
            }

            // Host the add-ins in the external process.
            // Activate the add-in.
            addin = addinToken.Activate<IEntryPoint>(this.addInProcess, AddInSecurityLevel.FullTrust);
            addin.Initialize(serviceProvider);
            addin.InitializeDataBindings();
            addin.FinishInitialization();
            addInList.Add(addin);
        }

        private void AddInProcessExited(object sender, EventArgs args)
        {
            addInProcess = null;
        }        

        private void StopDebugging()
        {
            if (!this.isDebugging)
                return;
            if (addInProcess != null)
            {
                addInProcess.Shutdown();
            }
            this.isDebugging = false;
        }

        private void ForcingUnloadCurrentAddins()
        {
            foreach (IEntryPoint inProcAddin in addInList)
            {
                if (inProcAddin != null)
                {                       
                    AddInController controller =
                        AddInController.GetAddInController(inProcAddin);
                    controller.Shutdown();
                }
                addInList.Clear();                
            }
        }

        #endregion //Private Methods
        #region Private Fields

        public delegate void DelegateToUnloadAddIns();

        private AddInProcess addInProcess;
        private const string exAddInPrefix = "/exAddIn:";
        private const string addInPathPrefix = "/addInPath:";
        private const string vstaHostDebugUriPrefix = "/vstaHostDebugUri:";
        private const string vstaHostDebugReadyPrefix = "/vstaHostDebugReady:";
        private IServiceProvider serviceProvider;
        private IHostItemProvider itemProvider;
        private ITypeMapProvider typeMapProvider;
        private ShapeApp.Application shapeappApp;
        private List<IEntryPoint> addInList;
        private string addInPath;
        private bool isDebugging = false;
        private bool isNDD = true;       

        #endregion //Private Fields



        #region PInvoke
        [DllImport("ole32.dll")]
        public static extern int GetRunningObjectTable(int reserved,
                                  out IRunningObjectTable prot);

        [DllImport("ole32.dll")]
        public static extern int CreateBindCtx(int reserved,
                                      out IBindCtx ppbc);
        #endregion

    }

    #region DTE
    [System.Runtime.InteropServices.ComImport]
    [System.Runtime.InteropServices.Guid("BA018599-1DB3-44f9-83B4-461454C84BF8")]
    public class DTE
    {

    }
    #endregion //DTE

    public class HostItemProvider : IHostItemProvider
    {
        #region Constructors, Destructors

        public HostItemProvider(ShapeApp.Application application)
        {
            this.application = application;
        }

        #endregion //Constructors, Destructors
        #region IHostItemProvider Members

        public object GetHostObject(Type primaryType, string primaryCookie)
        {
            if (primaryType == typeof(ShapeApp.Application))
            {
                return this.application;
            }
            else
            {
                throw new ArgumentOutOfRangeException();
            }
        }

        #endregion //IHostItemProvider Members
        #region Private Fields

        private ShapeApp.Application application;

        #endregion //Private Fields
    }

    public class HostTypeMapProvider : ITypeMapProvider
    {
        public HostTypeMapProvider()
        {
        }

        // Get the type name that corresponds to 
        // the canonical name from the host-provided maps.
        public Type GetTypeForCanonicalName(string canonicalName)
        {
            if (canonicalName == "ShapeApp, ShapeApp.Application")
            {
                return typeof(ShapeApp.Application);
            }

            if (canonicalName == "ShapeApp,
                ShapeApp.IApplicationEvents")
            {
                return typeof(ShapeApp.IApplicationEvents);
            }

            return null;
        }

        // Query the canonical name.
        public string GetCanonicalNameForType(Type type)
        {
            return null;
        }
    }
}

See Also

Tasks

How to: Start the IDE

How to: Exit the IDE

Walkthrough: Incorporating the IDE for a Managed Object Model

Concepts

Incorporating the Integrated Development Environment

Configuring the IDE

Integrating Help into the IDE

Add-In Debugging

Deploying the IDE and Runtime

Other Resources

Visual Studio Tools for Applications 2.0