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
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}
Create a DWORD subkey called AllThreadsRunOnFuncEval.
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.
[C#]
// 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.