Walkthrough: Incorporating the IDE for a Managed Object Model
This walkthrough provides step-by-step instructions for incorporating the integrated development environment (IDE) into the ShapeAppBasicCSharp sample application. It includes code and procedures that prepare the IDE to handle non-destructive debugging.
You can perform this walkthrough as a stand-alone walkthrough, or you can perform it as part of the series of walkthroughs described in Walkthrough: Integrating Visual Studio Tools for Applications with ShapeApp.
This walkthrough illustrates the following tasks:
Starting up and shutting down the IDE non-destructively.
Loading and unloading add-ins.
Attaching IDE startup code to a host event.
Registering a class to receive debug event notifications.
Handling the debug events.
Prerequisites
You need the following components to complete this walkthrough:
Visual Studio 2008.
Microsoft Visual Studio Tools for Applications 2.0.
The ShapeAppBasicCSharp sample extracted to the %SYSTEMDRIVE%\ShapeAppSamples\ShapeAppBasicCSharp folder on the development computer and registered. For more information about how to extract and register ShapeAppBasicCSharp, see How to: Build and Run the ShapeAppBasicCSharp Sample.
Building the Sample Application
To begin, open and build the ShapeAppBasicCSharp sample application in Visual Studio.
To build the ShapeAppBasicCSharp solution
Start Visual Studio 2008. If you are using Windows Vista as your operating system, start Visual Studio 2008 by using the Run as Administrator option.
On the File menu, point to Open, and then click Project/Solution.
In the Open Project dialog, navigate to the %SYSTEMDRIVE%\ShapeAppSamples\ShapeAppBasicCSharp folder.
Select the ShapeAppBasicCSharp.sln file, and then click Open.
On the Build menu, click Build Solution. Verify that the solution builds without errors.
Adding References to the Project
The ShapeAppBasicCSharp project needs references to some Microsoft Visual Studio Tools for Applications 2.0 and .NET Framework assemblies. These references must be in the project before you can incorporate the IDE.
To add the required references
In Solution Explorer, right-click References under the ShapeAppBasicCSharp project, and then click Add Reference.
The Add Reference dialog box opens.
Click the .NET tab and add references to the following assemblies:
Microsoft.VisualStudio.Tools.Applications.DesignTime.v9.0
Microsoft.VisualStudio.Tools.Applications.ProgrammingModel
Click the COM tab and add a reference to the following type library:
DTEProvider 1.0 Type Library in the %CommonProgramFiles%\Microsoft Shared\VSTA\9.0 directory
Microsoft Development Environment 8.0
Click OK.
Starting Up and Shutting Down the IDE Non-Destructively
Non-destructive debugging involves closing down the host application gracefully when a debugging session ends. The following code closes the external debug process and unloads add-ins before closing the host application and the IDE. This helps prevent processes from continuing to run after the application closes, which can happen if the main application process is terminated abruptly.
The code also opens a project automatically for the add-in developer when the IDE starts.
To start up and shut down the IDE non-destructively
In Solution Explorer, right-click the ShapeAppBasicCSharp project, point to Add on the shortcut menu, and then click Class.
Change the name of the class to VstaDesignTimeIntegration.cs, and then click Add.
In VstaDesignTimeIntegration.cs, replace the using statements with the following list.
using System; using System.AddIn.Hosting; using System.IO; using System.Threading; using System.Windows.Forms; using Microsoft.VisualStudio.Tools.Applications.DesignTime; using Microsoft.VisualStudio.Tools.Applications.DesignTime.Interop; using VSTADTEProvider.Interop;
Replace the definition of the VstaDesignTimeIntegration class with the following code.
internal sealed class VstaDesignTimeIntegration : MarshalByRefObject { }
Add the following fields to the class.
private Application application; private bool isShuttingDown; private bool isDebugging; private bool isDebugHostRegistered; private bool reloadInProc; private AddInProcess macroAddInProcess; private EnvDTE.Project macroProject; private EnvDTE.DTE dte; private EnvDTE.BuildEvents buildEvents; private EnvDTE.SolutionEvents solutionEvents; private EnvDTE.DTEEvents dteEvents; private static readonly String MacroProjectFilePath = Path.Combine(System.Environment.GetFolderPath( Environment.SpecialFolder.MyDocuments), @"ShapeAppCSharp\Macros\ShapeAppMacros.csproj");
Add the following method to the VstaDesignTimeIntegration class. This method is called after add-ins are loaded by ShapeApp, but before the ShapeApp UI appears.
internal void Connect(Application hostApplication) { this.reloadInProc = true; this.application = hostApplication; }
Add the following event handler to shut down the add-in process when the external debug process ends.
private void MacroAddInProcessExiting(object sender, System.ComponentModel.CancelEventArgs args) { this.macroAddInProcess.ShuttingDown -= MacroAddInProcessExiting; this.macroAddInProcess = null; }
Add the following method to unload add-ins.
internal void Disconnect() { this.isShuttingDown = true; // Stop debugging and close the IDE. if (this.dte != null) { if (this.dte.Mode == EnvDTE.vsIDEMode.vsIDEModeDebug || this.dte.Debugger.DebuggedProcesses.Count > 0) { // Do not load the add-in back into the // host application until the debugger stops. this.reloadInProc = false; this.dte.Debugger.Stop(true); } // Exit the IDE. this.dte.Quit(); } // Unload the external add-in process. if (this.macroAddInProcess != null) { this.macroAddInProcess.ShuttingDown -= MacroAddInProcessExiting; this.macroAddInProcess.Shutdown(); this.macroAddInProcess = null; } }
Add the following methods to obtain the DTE instance that is used to start the IDE and to handle events. The buildEvents_OnBuildBegin event handler unloads the add-in when the build process begins. The buildEvents_OnBuildDone event handler copies the add-in to the user profile directory. The dteEvents_OnBeginShutdown event handler deletes event handlers when the IDE shuts down. Finally, the solutionEvents_AfterClosing event handler releases memory that was set aside for the add-in.
private void EnsureIDE() { if (this.dte == null) { IDTEProvider dteProvider = (IDTEProvider)new VSTADTEProviderClass(); this.dte = (EnvDTE.DTE)dteProvider.GetDTE("ShapeAppCSharp", 0); if (this.dte == null) throw new InvalidOperationException("Cannot start the IDE."); // Save event sync locations. this.buildEvents = dte.Events.BuildEvents; this.solutionEvents = dte.Events.SolutionEvents; this.dteEvents = dte.Events.DTEEvents; this.buildEvents.OnBuildBegin += new EnvDTE._dispBuildEvents_OnBuildBeginEventHandler(buildEvents_OnBuildBegin); this.buildEvents.OnBuildDone += new EnvDTE._dispBuildEvents_OnBuildDoneEventHandler(buildEvents_OnBuildDone); this.solutionEvents.AfterClosing += new EnvDTE._dispSolutionEvents_AfterClosingEventHandler(solutionEvents_AfterClosing); this.dteEvents.OnBeginShutdown += new EnvDTE._dispDTEEvents_OnBeginShutdownEventHandler(dteEvents_OnBeginShutdown); } } void buildEvents_OnBuildBegin(EnvDTE.vsBuildScope Scope, EnvDTE.vsBuildAction Action) { // Unload the add-in. this.application.VstaRunTimeIntegration.UnloadCurrentMacroAddin(false); } private void buildEvents_OnBuildDone(EnvDTE.vsBuildScope Scope, EnvDTE.vsBuildAction Action) { if (dte.Solution.SolutionBuild.LastBuildInfo == 0) { // Copy the updated add-in to be discovered by the runtime. string dir = Path.Combine(Path.GetDirectoryName(VstaDesignTimeIntegration.MacroProjectFilePath), "bin"); string macroAssemblyPath = Path.Combine(dir, this.dte.Solution.SolutionBuild.ActiveConfiguration.Name); string assemblyPath = Path.Combine(macroAssemblyPath, @"ShapeAppMacros.dll"); if (File.Exists(assemblyPath)) { // Copy the add-in to the user profile directory. File.Copy(assemblyPath, VstaRunTimeIntegration.MacroAddInPath, true); } } else { MessageBox.Show( "Macro project build failed.", "ShapeAppCSharp", MessageBoxButtons.OK, MessageBoxIcon.Error); } // Load macros. this.application.VstaRunTimeIntegration.LoadMacros(null); } private void dteEvents_OnBeginShutdown() { this.buildEvents.OnBuildBegin -= new EnvDTE._dispBuildEvents_OnBuildBeginEventHandler(buildEvents_OnBuildBegin); this.buildEvents.OnBuildDone -= new EnvDTE._dispBuildEvents_OnBuildDoneEventHandler(buildEvents_OnBuildDone); this.solutionEvents.AfterClosing -= new EnvDTE._dispSolutionEvents_AfterClosingEventHandler(solutionEvents_AfterClosing); this.dte = null; } private void solutionEvents_AfterClosing() { this.macroProject = null; }
Add the following method to the VstaDesignTimeIntegration class. This method uses the DTE class that is created in the EnsureIDE method to start the IDE. It then calls the OpenMacroProject or CreateNewMacroProject helper methods to open an existing macro project or create a new macro project. You will add the OpenMacroProject and CreateNewMacroProject methods in the next few steps.
internal bool ShowIde() { EnsureIDE(); if (this.dte == null) return false; this.dte.MainWindow.Visible = true; if (File.Exists(VstaDesignTimeIntegration.MacroProjectFilePath)) { OpenMacroProject(); } else { CreateNewMacroProject(); } return true; }
Add the following method to the VstaDesignTimeIntegration class. The OpenMacroProject method checks to see whether a macro project is already loaded. If not, it loads the macro project. The // Register the external debug process comment is included in this method to mark where you will add code in a later step.
private void OpenMacroProject() { // See if the project is already loaded. foreach (EnvDTE.Project currProject in this.dte.Solution.Projects) { string projPath = currProject.FileName; if (String.Compare(projPath, VstaDesignTimeIntegration.MacroProjectFilePath, StringComparison.OrdinalIgnoreCase) == 0) { this.macroProject = currProject; // Register the external debug process. return; } } // Load the project because it is not currently loaded. this.macroProject = this.dte.Solution.AddFromFile(VstaDesignTimeIntegration.MacroProjectFilePath, true); // Register the external debug process. }
Add the following method to the VstaDesignTimeIntegration class. The CreateNewMacroProject method uses the ShapeAppCSharp macro project template to create a new macro project. The Visual Studio Tools for Applications IDE starts and opens this macro project in Project Explorer. The // Register the external debug process comment is included in this method to mark where you will add code in a later step.
private void CreateNewMacroProject() { EnvDTE80.Solution2 sol = (EnvDTE80.Solution2)this.dte.Solution; string macroTemplatePath = sol.GetProjectTemplate("ShapeAppCSharp-AppLevel.zip", "CSharp"); if (String.IsNullOrEmpty(macroTemplatePath)) throw new FileNotFoundException("ShapeAppCSharp application-level project template has not been registered."); this.dte.Solution.AddFromTemplate( macroTemplatePath, Path.GetDirectoryName(VstaDesignTimeIntegration.MacroProjectFilePath), Path.GetFileNameWithoutExtension(VstaDesignTimeIntegration.MacroProjectFilePath), true); // Iterate over the project collection. foreach (EnvDTE.Project currProject in this.dte.Solution.Projects) { string projPath = currProject.FileName; if (String.Compare(projPath, VstaDesignTimeIntegration.MacroProjectFilePath, StringComparison.OrdinalIgnoreCase) == 0) { this.macroProject = currProject; // Install the macro add-in assembly into the // user's Documents folder. this.macroProject.Properties.Item("PostBuildEvent").Value = "cscript \"$(ProjectDir)InstallAddIn.js\" \"$(TargetPath)\" \"ShapeAppCSharp\\MacroAddIns\""; // Register the external debug process } } }
To create the IDE objects
Right-click Application.cs, and then click View Code.
Add the following properties to the Application class. This creates objects that the ShapeApp sample uses to access the Visual Studio Tools for Applications IDE.
internal VstaRunTimeIntegration VstaRunTimeIntegration { get; set; } internal VstaDesignTimeIntegration VstaDesignTimeIntegration { get; set; }
Loading and Unloading Add-Ins
When non-destructive debugging with an existing host application starts, the add-in is unloaded from the host application and into the external debug process. After debugging ends, the add-in is unloaded from the external process and back into the host application. For more information, see Add-In Debugging.
There are three methods you must add to the VstaRunTimeIntegration.cs code file to complete these tasks.
To load and unload add-ins
Right-click VstaRunTimeIntegration.cs, and then click View Code.
Add the following fields to the VstaRunTimeIntegration class.
private const String MacrosStartUpClass = "ShapeAppMacros.AppAddIn"; private static readonly String AppLevelAddInPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments), @"ShapeAppCSharp\AppAddIns"); internal static readonly String MacroAddInPath = Path.Combine(System.Environment.GetFolderPath(System.Environment.SpecialFolder.MyDocuments), @"ShapeAppCSharp\MacroAddIns\ShapeAppMacros.dll"); private IEntryPoint macroAddIn;
Add the following method to search for the add-in at the specified location using the Visual Studio Tools for Applications add-in pipeline. After the add-in is discovered, its token is used to load the add-in.
private IEntryPoint LoadAddIn(string addInPath, string startUpClass, AddInProcess addInProcess) { // Discover the add-in from a known location. Collection<AddInToken> addInToken = AddInStore.FindAddIn(typeof(IEntryPoint), AddInStoreExtensions.DefaultPipelinePath, addInPath, startUpClass); // If the add-in is not valid, return false. if (addInToken.Count == 0) return null; IEntryPoint addIn = null; // Load the add-in using its token. if (addInProcess == null) { // Load the add-in into the same process as the host. addIn = addInToken[0].Activate<IEntryPoint>(AddInSecurityLevel.FullTrust); } else { // Load the add-in into an external process. addIn = addInToken[0].Activate<IEntryPoint>(addInProcess, AddInSecurityLevel.FullTrust); } // Initialize the add-in. addIn.Initialize(this.serviceProvider); addIn.InitializeDataBindings(); addIn.FinishInitialization(); // Return the loaded add-in. return addIn; }
Add the following method to load the add-in into the process that is passed in as a parameter. For debugging, the add-in is loaded into the external debug process.
internal void LoadMacros(AddInProcess externalProcess) { if (File.Exists(VstaRunTimeIntegration.MacroAddInPath)) { this.macroAddIn = LoadAddIn( VstaRunTimeIntegration.MacroAddInPath, VstaRunTimeIntegration.MacrosStartUpClass, externalProcess); } }
Add the following method to unload the current add-in from the host application process.
internal void UnloadCurrentMacroAddin(bool forcing) { if (this.macroAddIn != null) { // Check that this is not the debugged add-in. if (!forcing) { this.macroAddIn.OnShutdown(); } // Shut down the add-in. AddInController controller = AddInController.GetAddInController(this.macroAddIn); controller.Shutdown(); } this.macroAddIn = null; }
To update the ShapeApp application
Right-click Program.cs, and then click View Code.
Replace the code in the #region VSTA Integration Code region in the Program.cs code file. This code updates the ShapeApp application to access the IDE and the external process for debugging.
application.VstaRunTimeIntegration = new VstaRunTimeIntegration(); application.VstaDesignTimeIntegration = new VstaDesignTimeIntegration(); application.VstaRunTimeIntegration.Connect(application); application.VstaDesignTimeIntegration.Connect(application);
Attaching IDE Startup Code to a Host Event
You can implement several ways for add-in developers to start the IDE. For this walkthrough, attach the startup code to a ShapeAppBasicCSharp menu command. The code in the following section starts the Visual Studio Tools for Applications IDE and opens the macro project that is included with the ShapeAppCSharp sample application.
To attach the IDE to a host event
In Solution Explorer, right click ApplicationForm.cs, and then click View Designer.
In the ShapeApp application, click the top menu bar.
Type Here appears.
Type Tools.
Click the Tools menu.
The Tools submenu appears.
Click Type Here.
Type Launch IDE.
Press Enter.
Click the Tools menu.
Right-click Launch IDE.
Click Properties.
In the Properties window for launchIDEToolsStripMenuItem, click the drop-down menu for ShortcutKeys.
In the Modifiers pane, click Alt.
In the Key pane, click the drop-down menu, and then click F11.
Click inside the Properties window to verify your selection.
Click the Tools menu, and then double-click Launch IDE.
The ApplicationForm.cs code view appears.
Replace the launchIDEToolStripMenuItem_Click event handler to open the IDE with the following code.
private void launchIDEToolStripMenuItem_Click(object sender, EventArgs e) { this.application.VstaDesignTimeIntegration.ShowIde(); }
Testing the Visual Studio Tools for Applications IDE
Now you can test the ShapeAppBasicCSharp application to verify that the Visual Studio Tools for Applications IDE starts.
To test the Visual Studio Tools for Applications IDE
Compile and run ShapeAppBasicCSharp.
The ShapeAppCSharp application starts.
Note There are four warnings about the following fields: isShuttingDown, isDebugging, isDebugHostRegistered, and reloadInproc. These will be used in the next steps for enabling debugging.
In ShapeAppCSharp, click Tools, and then click Launch IDE.
The Visual Studio Tools for Applications IDE starts and opens a default macro project.
Exit the Visual Studio Tools for Applications IDE.
Exit ShapeAppCSharp.
Enabling Non-Destructive Debugging Using a Running Host
When you use an existing instance of the host application to run the add-in during the debugging session, it usually creates a better user experience than starting a new instance of the application. For more information about different ways to enable add-in debugging, see Add-In Debugging.
To enable this type of debugging, you need to modify the host application to receive event notifications from the debugger. You can then handle these notifications to load and unload add-ins.
Registering a Class to Receive Debug Event Notifications
Register the VstaDesignTimeIntegration class to receive event notifications from the debugger.
To register the class to receive event notifications
In Solution Explorer, open VstaDesignTimeIntegration.cs.
Change the VstaDesignTimeIntegration class to inherit from the IExternalDebugHost interface. This interface receives event notifications from the debugger.
internal sealed class VstaDesignTimeIntegration : MarshalByRefObject, IExternalDebugHost
Add the RegisterAsDebugHost method to the VstaDesignTimeIntegration class. This code registers the VstaDesignTimeIntegration class to receive event notifications when the debug process ends. The RegisterExternalDebugHost method returns a unique identifier. This identifier is saved to a variable and passed to the SetDebugInfo method of the IVstaHostAdapter object. This code obtains a path to the external process executable. Use the SetDebugInfo method of the IVstaHostAdapter to create a registry entry that contains the location of the external process executable and the command-line arguments to pass to that executable.
internal void RegisterAsDebugHost() { if (isDebugHostRegistered) return; IVstaHostAdapter vha = (IVstaHostAdapter)this.macroProject.get_Extender("VSTAHostAdapter2007"); string hostDebugUri = ExternalDebugging.RegisterExternalDebugHost((IExternalDebugHost)this, "ShapeAppCSharp"); string debugCommandLine = "/vstaHostDebugUri:\"" + hostDebugUri + "\""; vha.SetDebugInfo("ShapeAppCSharp.exe", debugCommandLine, ""); isDebugHostRegistered = true; }
Update the OpenMacroProject method. Locate the // Register the external debug process comments, and add the following code after each instance of the comment. This opens a project and registers an external process for debugging purposes.
RegisterAsDebugHost();
Update the CreateNewMacroProject method. Locate the // Register the external debug process comments, and add the following code after each instance of the comment. This creates a project and registers an external process for debugging purposes.
RegisterAsDebugHost();
Handling the Debug Events
When the add-in developer starts the debugger, unload the macro add-in from the host application process and then load that macro add-in into the debug process.
In addition, register an event handler to receive notifications when the debug session ends. For more information, see Add-In Debugging.
To handle the debug events
In the VstaDesignTimeIntegration.cs code file, add the OnDebugBeforeStarting, OnDebugStarting, and OnDebugStarting methods to the VstaDesignTimeIntegration class. These methods implement the OnBeforeDebugStarting, OnDebugStarting, and OnDebugStopping methods of the IExternalDebugHost interface. These methods are called when you start and stop the debugger. The EnsureMacroAddInProcess method uses this instance to create the external process that is required to load the add-in for debugging purposes. The LoadMacros method loads the add-in into the external process. For more information, see Discovering and Loading Add-Ins.
int IExternalDebugHost.OnBeforeDebugStarting() { // Create an external process. EnsureMacroAddInProcess(); return this.macroAddInProcess.ProcessId; } void IExternalDebugHost.OnDebugStarting() { if (!this.isDebugging) { this.application.VstaRunTimeIntegration.UnloadCurrentMacroAddin(false); // Load the add-in into the external debug process. this.application.VstaRunTimeIntegration.LoadMacros(this.macroAddInProcess); this.isDebugging = true; } else { MessageBox.Show("Cannot start multiple debug sessions."); } } void IExternalDebugHost.OnDebugStopping() { if (!this.isDebugging || this.isShuttingDown) return; this.application.VstaRunTimeIntegration.UnloadCurrentMacroAddin(true); // Do not reload the macro if a new macro is being recorded. if (this.reloadInProc) { this.application.VstaRunTimeIntegration.LoadMacros(null); } this.reloadInProc = true; this.isDebugging = false; }
Add the following method to the VstaDesignTimeIntegration class. The EnsureMacroAddInProcess method creates an external process to load the add-in for debugging purposes and sets its ShuttingDown event handler.
private void EnsureMacroAddInProcess() { if (this.macroAddInProcess != null) return; // Create an external process. this.macroAddInProcess = new AddInProcess(); this.macroAddInProcess.Start(); // Hook up the event handler. this.macroAddInProcess.ShuttingDown += MacroAddInProcessExiting; }
Testing the Debugger
Now you can test ShapeAppBasicCSharp by starting the Visual Studio Tools for Applications IDE and debugging a default macro project.
To test the debugger
Compile and run ShapeAppBasicCSharp.
The ShapeApp application starts.
In ShapeApp, on the Tools menu, click Launch IDE.
The Visual Studio Tools for Applications IDE starts and opens a default macro project.
In Project Explorer, double-click AppAddin.cs.
Set a breakpoint next to this line of code:
public void AppAddIn_Startup(object sender, EventArgs e)
On the Debug menu, click Start Debugging.
Execution stops at the breakpoint next to public void AppAddIn_Startup(object sender, EventArgs e).
Exit the Visual Studio Tools for Applications IDE.
Exit ShapeAppBasicCSharp.
Next Steps
If you performed this walkthrough as part of the comprehensive series of walkthroughs for the managed ShapeApp sample, return to Walkthrough: Integrating Visual Studio Tools for Applications with ShapeApp.
If you performed this walkthrough as a stand-alone walkthrough, there are several other steps you can perform to complete the process of integrating Visual Studio Tools for Applications into ShapeApp:
Generate proxies for the ShapeApp object model. For more information, see Walkthrough: Creating a Proxy Assembly.
Create add-in project templates. For more information, see Creating Project Templates Using the Project Template Generation Tool (Projectgen.exe).
Load and unload add-ins. For more information, see Walkthrough: Modifying an Application to Load Add-Ins.
See Also
Tasks
How to: Enable Non-Destructive Debugging for Add-Ins