Add-In Debugging

When you implement Visual Studio Tools for Applications add-in debugging, you have a choice between three levels of debugging functionality. These levels build on each other, from simple to complex. The decision of how much functionality to add depends on the developer experience you want to provide, and the robustness required by the host application.

The three levels increase in complexity in the following order:

  • Standard Visual Studio-style debugging.

  • Non-destructive debugging that starts a new instance of the host application.

  • Non-destructive debugging that works with a running host application.

If you experience debugging problems, see Troubleshooting Add-in Debugging for potential workarounds.

Standard Visual Studio-Style Debugging

The first level of add-in debugging is the default Visual Studio Tools for Applications debugger, which you can use without modifying the host application. This approach requires little configuration and is the simplest option for providing add-in debugging support.

Visual Studio Tools for Applications creates a new instance of the host application and runs that instance in the same process as the add-in that is being debugged. When the user exits the debugger, Visual Studio Tools for Applications terminates the host application process. As a result, the add-in and the host application are forcefully terminated at that exact point in the execution.

This approach works as long as the host application can be forcefully terminated at an arbitrary point during execution without causing problems. For many host applications, this is not acceptable. Terminating the application without performing clean-up tasks could lead to corrupt files and other problems. In these cases, you might want the host application to be notified when the debug session terminates so the host application can perform the necessary clean-up tasks. To enable these notifications, consider setting up a non-destructive debugging environment.

Implementing Standard Visual Studio-Style Debugging

To implement standard debugging, perform the following task:

  • Set the DebugInfoExeName and DebugInfoCommandLine elements in the project template file.

You can set these values by using the Project Template Generation tool (Projectgen.exe). For more information, see Descriptor Schema for Projectgen.exe.

Non-Destructive Debugging That Starts a New Instance of the Host Application

The second level of add-in debugging is a basic non-destructive implementation. Non-destructive debugging gives the host application an opportunity to perform any clean-up tasks that are necessary to exit properly. To enable this, you can configure the host application to receive notifications when the debug session starts and ends.

With non-destructive debugging, the host application and the add-in run in separate processes. Although this approach is more robust, communicating between processes can decrease performance.

You must modify the host application to receive events from the debugger. Visual Studio Tools for Applications starts the debugger in an external debug process. This process uses .NET Framework remoting to search for a registered instance of the IExternalDebugHost interface. Then, you can handle debug event notifications to unload and load add-ins or perform clean-up tasks to properly exit the application.

Implementing Basic Non-Destructive Debugging

To implement basic non-destructive debugging, perform the following tasks in the host application:

  • Set the DebugInfoExeName and DebugInfoCommandLine elements in the project template file.

  • Parse the vstaHostDebugUri and vstaHostDebugReady arguments to retrieve the values set by Visual Studio Tools for Applications.

  • Register the host application to receive debug event notifications.

  • Handle debug events.

Set the DebugInfoExeName and DebugInfoCommandLine Elements

You can set these values by using the Project Template Generation tool (Projectgen.exe). For more information, see Descriptor Schema for Projectgen.exe.

Parse Debug Arguments Passed into the Host Application

You must parse the vstaHostDebugUri and vstaHostDebugReady values that are set by Visual Studio Tools for Applications. This information is used to register the host application to receive debug event notifications. The vstaHostDebugUri argument begins with the string "/vstaHostDebugUri:", which is stored in the hostDebugUri variable in the following example. The vstaHostDebugReady argument begins with the string "/vstaHostDebugReady:", which is stored in the hostDebugReadyEventName variable in the following example.

The following code example demonstrates how to retrieve and save the vstaHostDebugUri and vstaHostDebugReady values. For a complete example of non-destructive debugging that starts a new instance of the host application, see How to: Enable Non-Destructive Debugging for Add-Ins.

private const string vstaHostDebugUriPrefix = "/vstaHostDebugUri:";
private const string vstaHostDebugReadyPrefix = "/vstaHostDebugReady:";
string[] args = Environment.GetCommandLineArgs(); 

for (int i = 1; i < args.Length; ++i)
{
    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);
    }
}

Register the Host Application to Receive Debug Event Notifications

To register the host application to receive debug event notifications, create a class that both inherits from and implements the IExternalDebugHost interface. If you have a class in the host application that contains code to start the IDE, you can add the interface implementation to that class.

Pass the hostDebugUri value to the RegisterExternalDebugHost method. Next, pause the start of debugging until the new instance of the host application is running by setting the manual reset event using the hostDebugReadyEventName value.

The following code example shows an IDE integration class that inherits from the IExternalDebugHost interface.

public class VstaRunTimeIntegration : IExternalDebugHost

The following code example demonstrates how to register the host application, pass the hostDebugUri value, register for debug event notifications, and set the manual reset event with the hostDebugReadyEventName value.

ExternalDebugging.RegisterExternalDebugHost(
    (IExternalDebugHost)this, new Uri(hostDebugUri));
EventWaitHandle readyEvent = new EventWaitHandle(false,
    EventResetMode.ManualReset, hostDebugReadyEventName);
readyEvent.Set();

Handle Debug Events

When you implement the IExternalDebugHost interface, follow these guidelines:

Because Visual Studio Tools for Applications starts a new instance of the host application, you do not need to unload add-ins from the host application process.

The following code example shows how to create a new process for external debugging, start the process, create and register an event handler that can receive notifications when the debug session ends, and return the process ID.

public int OnBeforeDebugStarting()
{
    // Create the process and start it.
    this.addInProcess = new AddInProcess();
    this.addInProcess.Start();

    // Hook up the event handlers.
    addInProcess.ShuttingDown += new 
        EventHandler<System.ComponentModel.CancelEventArgs>(
            AddInProcessExiting);
    isDebugging = true;
    return addInProcess.ProcessId;
}

When the external debug process finds a registered class, it calls the OnDebugStarting method. The following code example shows how to create an IEntryPoint, find a specific add-in, and activate the add-in.

public void OnDebugStarting()
{
    IEntryPoint addin = null;
    Collection<AddInToken> token = AddInStore.FindAddIn(
        typeof(IEntryPoint), 
        AddInStoreExtensions.DefaultPipelinePath, 
        addInPath,className);

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

When debugging stops, close the external debug process that was created in OnBeforeDebugStarting. The following code example shows how to close the external debug process.

public void OnDebugStopping()
{
    if (isDebugging)
    {
        addInProcess.Shutdown();
        isDebugging = false;
    }
}

Non-Destructive Debugging That Works with a Running Host Application

The third level of add-in debugging is non-destructive debugging that works with a running instance of the host application. In standard Visual Studio-style debugging and basic non-destructive debugging, Visual Studio Tools for Applications starts a new instance of the host application to run the add-in. However, you can use an existing instance of the host application to run the add-in during the debug session. This level of debugging does not forcefully close the host application, and it provides a more natural user experience for the add-in developer. This interactive approach provides an experience very similar to that of a Visual Basic for Applications (VBA) macro.

As with non-destructive debugging, you need to configure the host application to receive notifications when the debug session starts and ends. When the debug session starts, the host application receives a notification. The host application responds by unloading the add-in from the host process and then loading the add-in into the debug process. When the debug session ends, the host application receives another notification. The host application loads the add-in back into the host application process.

During an out-of-process debugging session, the Visual Studio Tools for Applications debugger, the add-in, and the host application each run in separate processes as they perform the following tasks. 

  • The debugger process evaluates symbol names and functions on the thread for the add-in process.

  • When the debugger evaluates a host object in the add-in process, the add-ins calls a host function, which is executed by Visual Studio Tools for Applications.

  • The Visual Studio Tools for Applications runtime calls the add-in process to retrieve add-in lifetime management information.

Because the add-in process is frozen by the debugger and only one thread is allowed to evaluate variables, the call from the host cannot be serviced. Thus, the add-in process stops responding.

To resolve this problem, the registry can be modified to instruct the debugger to enable execution of all threads when evaluating an object. When all threads execute, the add-in process can service the call from the host process, and the debugger is not blocked. However, this workaround may cause side effects if the add-in has multiple threads scheduled to run when the debugger breaks.

If you want Visual Studio Tools for Applications to use all threads to evaluate a variable, you must set a registry key.

The following procedure demonstrates how to configure the debugger to use all threads.

To debug your add-in using all threads

  1. In the registry, navigate to the following key. Replace HostID in the path with the name of your add-in. For example, you can replace HostID with ShapeAppCSharp.

    HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VSTAHost\HostID\9.0\AD7Metrics\Engine\{449EC4CC-30D2-4032-9256-EE18EB41B62B}

  2. Create a DWORD subkey called AllThreadsRunOnFuncEval.

  3. Assign the value of dword:00000001.

Implementing Advanced Non-Destructive Debugging

To enable non-destructive debugging with an add-in that is already running, perform the following actions:

  • Set the DebugInfoExeName and DebugInfoCommandLine elements.

  • Assign the vstaHostDebugUri value programmatically.

  • Register the host application to receive debug event notifications. When debugging starts, unload the add-in from the host application process, and load the add-in into the debug process. When debugging ends, unload the add-in from the debug process, and load the add-in into the host application process.

  • Handle debug events.

Set Debug Information, Assign the Debug Argument, and Register the Host Application to Receive Debug Event Notifications

You can set the DebugInfoExeName element programmatically by using the SetDebugInfo method in the IVstaHostAdapter class, which is implemented by Visual Studio Tools for Applications.

Create a class that both inherits from and implements the IExternalDebugHost interface. If you have a class in the host application that contains code to start the IDE, you can add the interface implementation to that class.

In the class that implements the IExternalDebugHost interface, call the RegisterExternalDebugHost method. Assign the return value to the vstaHostDebugUri value. Because you are using a running instance of the host application, you do not have to set a separate event to send a notification that the debugging can begin.

The following code example demonstrates how to set the DebugInfoExeName value, assign the vstaHostDebugUri element programmatically, and register the host application for debug notifications. For a complete sample, see How to: Build and Run the ShapeAppMacroRecordingCSharp Sample.

IVstaHostAdapter vha = (IVstaHostAdapter)macroProject.get_Extender(
    "VSTAHostAdapter2007");
string hostDebugUri = ExternalDebugging.RegisterExternalDebugHost(
    (IExternalDebugHost)this, "ShapeAppCSharp");
string debugCommandLine = "/vstaHostDebugUri:\"" + 
    hostDebugUri + "\"";
vha.SetDebugInfo("ShapeAppCSharp.exe", debugCommandLine,"");

Handle Debug Events

When you implement the IExternalDebugHost interface, follow these guidelines:

  • Use OnBeforeDebugStarting to create the external debug process and register an event handler that can receive notifications when the debug session ends.

  • Use OnDebugStarting to unload the add-in from the host application and load the add-in into the external debug process.

  • Use OnDebugStopping to unload the add-in from the external process and back into the host application.

For more information about loading add-ins into an external process, see How to: Activate Add-ins with Different Isolation and Security Levels. For more information about loading and unloading add-ins, see Add-in Discovery and Add-in Activation.

The following code example shows how to create a new process for external debugging, start the process, create and register an event handler that can receive notifications when the debug session ends, and return the process ID. For a complete sample, see How to: Build and Run the ShapeAppMacroRecordingCSharp Sample.

// Create an external process to use for debugging.
int IExternalDebugHost.OnBeforeDebugStarting()
{
    // Create an external process.
    this.macroAddInProcess = new AddInProcess();
    this.macroAddInProcess.Start();

    // Watch for when the user finishes debugging.
    this.macroAddInProcess.ShuttingDown += MacroAddInProcessExiting;
    return this.macroAddInProcess.ProcessId;
}

When the external debug process finds a registered class, it calls the OnDebugStarting method. The following code example shows how to create an IEntryPoint, unload add-ins from the host application, load add-ins into the external debug process, and activate the add-in.

// Called when debugging is started.
void IExternalDebugHost.OnDebugStarting()
{
    this.isDebugging = true;
    IEntryPoint addIn = null;

    // Unload add-ins from the host application.
    foreach (IEntryPoint inProcAddin in macroAddIns)
    {
        if (inProcAddin != null)
        {
            if (!isDebugging)
            {
                inProcAddin.OnShutdown();
            }
            AddInController controller = 
                AddInController.GetAddInController(inProcAddin);
            controller.Shutdown();
        }
    }
    macroAddIns.Clear();

    // Load the add-in into the external debug process.
    Collection<AddInToken> addInToken = 
        AddInStore.FindAddIn(typeof(IEntryPoint), 
        AddInStoreExtensions.DefaultPipelinePath, 
        addInPath, 
        startUpClass);

    // Activate the add-in in the external debug process.
    addIn = addInToken[0].Activate<IEntryPoint>(
        this.macroAddInProcess, AddInSecurityLevel.FullTrust);
    addIn.Initialize(this.serviceProvider);
    addIn.InitializeDataBindings();
    addIn.FinishInitialization();
}

When debugging stops, close the external debug process that was created in OnBeforeDebugStarting. The following code example shows how to close the external debug process.

// Called when debugging is stopped. 
void IExternalDebugHost.OnDebugStopping()
{
    this.isDebugging = false;
    macroAddInProcess.ShuttingDown -= MacroAddInProcessExiting;
    macroAddInProcess = null;

    // Unload add-ins from the external debug process.
    foreach (IEntryPoint inProcAddin in macroAddIns)
    {
        if (inProcAddin != null)
        {
            if (!isDebugging)
            {
                inProcAddin.OnShutdown();
            }
            AddInController controller = 
                AddInController.GetAddInController(inProcAddin);
            controller.Shutdown();
        }
    }
    macroAddIns.Clear();

    // Load the add-in into the host application.
    IEntryPoint addIn = null;
    Collection<AddInToken> addInToken = 
        AddInStore.FindAddIn(typeof(IEntryPoint), 
        AddInStoreExtensions.DefaultPipelinePath, 
        addInPath, 
        startUpClass);

    // Activate the add-in in the host application.
    addIn = addInToken[0].Activate<IEntryPoint>(
        AddInSecurityLevel.FullTrust);
    addIn.Initialize(this.serviceProvider);
    addIn.InitializeDataBindings();
    addIn.FinishInitialization();
}

For more information about how to load add-ins into an external process, see How to: Activate Add-ins with Different Isolation and Security Levels. For more information about loading and unloading add-ins, see Add-in Discovery and Add-in Activation.

Troubleshooting Add-in Debugging

In destructive and non-destructive debugging scenarios, the values that are shown for local variables or properties in the immediate window might actually be the value of the proxy object. You may see the following errors in the Locals or Immediate windows:

  1. System.Reflection.TargetException: Non-static method requires a target.

  2. Microsoft.Office.Tools.Debugger appears in the name column.

  3. The delayedEventList list appears, but it should not be visible.

  4. Cannot obtain fields or call methods on the instance of type type because it is a proxy to a remote object.

As a workaround, create a local variable and assign it to the value of the host, especially for struct variables. For example, if you want to the size of a shape in the host application, create a local size variable and set it to the value of the shape.size. Then, you can evaluate the local variable to test the value.

When you build and debug an out-of-process add-in multiple times, the DTE OnBuildBegin event may not fire. Because a previous version of the program database (.pdb) file may be loaded during the debug session, the build may fail. These problems may occur because the program database (.pdb) is still locked by the add-in process. You may see the following errors or error messages:

  1. Unexpected error creating debug information file 'c:\...\filename.pdb' - c:\...\filename.pdb: The process cannot access the file because it is being used by another process.

  2. The build succeeds, but the assembly cannot be unloaded, updated, or deleted.

As a workaround, ensure that host application shuts down the add-in process. For example, in the ShapeApp sample, you can call the this.macroAddInProcess.Shutdown() method, and ensure the current macro add-in is unloaded and the program database (.pdb) is deleted.

See Also

Tasks

How to: Start the IDE

How to: Exit the IDE

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

Walkthrough: Incorporating the IDE for a Managed Object Model

Concepts

Discovering and Loading Add-Ins

Incorporating the Integrated Development Environment

Configuring the IDE

Integrating Help into the IDE

Deploying the IDE and Runtime

Other Resources

Visual Studio Tools for Applications 2.0