TestExecWindow
Class VisualStudioConnector - Visual Studio: data access via DTE, notifications, automation commands

Overview:

Register for notifications at Visual Studio

Derive from notification interfaces:

/// Class derives from notification interfaces to get
/// informed when user performs changes within Visual Studio.
class VisualStudioConnector
: Microsoft.VisualStudio.Shell.Interop.IVsSelectionEvents
, Microsoft.VisualStudio.Shell.Interop.IVsUpdateSolutionEvents

Advise for notifications:

private void ConnectWithVisualStudio()
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
bool connectedToSelectionEvents = false;
bool connectedToUpdateSolutionEvents = false;
// Advise to selection events (e.g. startup project changed)
m_monitorSelection = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.
GetService(typeof(SVsShellMonitorSelection)) as IVsMonitorSelection;
if (m_monitorSelection != null)
{
m_monitorSelection.AdviseSelectionEvents(
this, out m_selectionEventsCookie);
connectedToSelectionEvents = true;
}
else
{
WriteLine(1, "ConnectWithVisualStudio: GetService(SVsShellMonitorSelection) failed!");
}
// Advise to update solution events (e.g. switched debug/release configuration)
m_solutionBuildManager = Microsoft.VisualStudio.Shell.ServiceProvider.GlobalProvider.
GetService(typeof(SVsSolutionBuildManager)) as IVsSolutionBuildManager;
if (m_solutionBuildManager != null)
{
m_solutionBuildManager.AdviseUpdateSolutionEvents(
this, out m_updateSolutionEventsCookie);
connectedToUpdateSolutionEvents = true;
}
else
{
WriteLine(1, "ConnectWithVisualStudio: GetService(SVsSolutionBuildManager) failed!");
}
if (connectedToSelectionEvents && connectedToUpdateSolutionEvents)
{
WriteLine(1, "ConnectWithVisualStudio succeeded");
}
}
catch (Exception ex)
{
WriteLine(1, "ConnectWithVisualStudio failed: EXCEPTION: " + ex.ToString());
}
}

Unadvise from notifications:

/// Release all connections to Visual Studio.
/// This function is called from Dispose method of main app class TestExecWindow
public void DisconnectFromVisualStudio()
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
if (m_monitorSelection != null && m_selectionEventsCookie != 0)
m_monitorSelection.UnadviseSelectionEvents(m_selectionEventsCookie);
if (m_solutionBuildManager != null && m_updateSolutionEventsCookie != 0)
m_solutionBuildManager.UnadviseUpdateSolutionEvents(m_updateSolutionEventsCookie);
}
catch (Exception ex)
{
WriteLine(1, "DisconnectFromVisualStudio: EXCEPTION: " + ex.ToString());
}
}

Receiving notifications from Visual Studio

Notification about a change of startup project:

// ----- Interface IVsSelectionEvents -----
// Check for notification about changed startup project
int IVsSelectionEvents.OnElementValueChanged(
uint elementid, object varValueOld, object varValueNew)
{
if (elementid == (uint)VSConstants.VSSELELEMID.SEID_StartupProject)
{
// When startup project is set in solution explorer a complete refresh is triggered
if (varValueNew != null)
{
WriteLine(2, "Detected new StartupProject");
m_mainEvents.OnRefreshAll();
}
}
return VSConstants.S_OK;
}
// All other events from IVsSelectionEvents are ignored.
// Eventhandler functions simply return OK status.
int IVsSelectionEvents.OnCmdUIContextChanged(
uint dwCmdUICookie, int fActive)
{
return VSConstants.S_OK;
}
/// ...

Notification about a change of configuration:

// ----- Interface IVsUpdateSolutionEvents -----
int IVsUpdateSolutionEvents.OnActiveProjectCfgChange(IVsHierarchy pIVsHierarchy)
{
WriteLine(2, "Detected OnActiveProjectCfgChange");
m_mainEvents.OnRefreshAll();
return VSConstants.S_OK;
}

Requesting project information via DTE

Get current configuration (e.g Debug or Release)

//Get name of config (e.g. "Debug", "Release")
string configName = dte.Solution.SolutionBuild.ActiveConfiguration.Name;

Get name of startup project

EnvDTE80.SolutionBuild2 sb = (EnvDTE80.SolutionBuild2)dte.Solution.SolutionBuild;
if (sb == null)
{
WriteLine(1, "SolutionBuild is null (checking for startup project is not possible)");
return false;
}
// sb.StartupProjects is an array of project file names. Usually we expect
// only a single project name within the array.
// Name is of the format "..\Test\TestRunner\TestRunner.vcxproj"
if (sb.StartupProjects == null)
{
WriteLine(2, "ReadSettingsOfStartupProject-End: StartupProjects not available");
return false;
}
foreach (String item in (Array)sb.StartupProjects)
{
msg += item;
}
WriteLine(3, "startupProjects=" + msg);
// for further processing extract the name without path and extension
// e.g. nameStartupProject = "TestRunner"
// (extraction code not shown here)

Get startup project interface

// Perform recursive search for the startup project through all solution filters:
EnvDTE.Project startupProj = FindProject(nameStartupProject);

Implementation detail: DTE allows iteration over projects

foreach (EnvDTE.Project project in dte.Solution.Projects)

Implementation detail: Hierarchical recursive search through tree of project items within method FindProjectRecursive:

foreach (EnvDTE.ProjectItem item in project.ProjectItems)
{
EnvDTE.Project realProject = item.Object as EnvDTE.Project;
if (realProject != null)
{
if (realProject.Kind == EnvDTE.Constants.vsProjectKindSolutionItems)
{
EnvDTE.Project projectNextLevel = FindProjectRecursive(
realProject, nameProject);
if (projectNextLevel != null)
{
return projectNextLevel;
}
}
else if (realProject.Name == nameProject)
{
return realProject;
}
}
}

Get name and path of generated executable (depends on previously found startupProj and configName)

// Get full path of executable depending on found
// startup project and configuration (Debug/Release)
Microsoft.VisualStudio.VCProjectEngine.VCProject vcProj = null;
try
{
vcProj = (Microsoft.VisualStudio.VCProjectEngine.VCProject)startupProj.Object;
if (vcProj ==null)
{
WriteLine(1, "Project is not of type VCProject and is not supported!");
return false;
}
}
catch (Exception ex)
{
WriteLine(2, "ReadSettingsOfStartupProject-End: EXCEPTION: " + ex.ToString());
WriteLine(1, "Project is not of type VCProject and is not supported!");
return false;
}
Microsoft.VisualStudio.VCProjectEngine.IVCCollection configs =
(Microsoft.VisualStudio.VCProjectEngine.IVCCollection)vcProj.Configurations;
Microsoft.VisualStudio.VCProjectEngine.VCConfiguration config =
FindConfig(configs, configName, m_platFormStartupProject);
if (config == null)
{
WriteLine(1, "Config " + configName + " not found");
return false;
}
msg = "PrimaryOutput (FullExePath)=" + config.PrimaryOutput;
WriteLine(2, msg);

Helper function to find a given configuration

private Microsoft.VisualStudio.VCProjectEngine.VCConfiguration FindConfig(Microsoft.VisualStudio.VCProjectEngine.IVCCollection in_configurations, String in_configName, String in_platform)
{
string expectedConfigName = in_configName + "|" + in_platform;
WriteLine(3, "FindConfig: searching for config " + expectedConfigName);
if ((in_configurations == null) || (in_configurations.Count <= 0))
{
WriteLine(3, "FindConfig: in_configurations is null or empty");
return null;
}
WriteLine(3, "FindConfig: configurations.Count=" + in_configurations.Count);
foreach (Microsoft.VisualStudio.VCProjectEngine.VCConfiguration configItem in in_configurations)
{
WriteLine(3, "FindConfig: configItem.Name=>" + configItem.Name + "< .ConfigurationName=>" + configItem.ConfigurationName);
if (configItem.Name == expectedConfigName)
{
return configItem;
}
}
return null;
}

Automate Visual Studio

Open a text file within Visual Studio

public void OpenFile(string in_fullFilePath, int in_lineNum = 0)
{
WriteLine(3, "OpenFile " + in_fullFilePath + " line " + in_lineNum);
try
{
ThreadHelper.ThrowIfNotOnUIThread();
dte.ItemOperations.OpenFile(in_fullFilePath, EnvDTE.Constants.vsViewKindTextView);
if (in_lineNum>0)
{
dte.ExecuteCommand("Edit.Goto",in_lineNum.ToString());
}
}
catch (Exception ex)
{
WriteLine(1, "Could not open file " + in_fullFilePath + "\nEXCEPTION: " + ex.ToString());
}
}

Start debugging with special command line options

public void StartDebugging(string in_cmdLineParams)
{
try
{
ThreadHelper.ThrowIfNotOnUIThread();
if (m_startupProj != null)
{
string cmdLineParams = "";
if (in_cmdLineParams != null)
{
cmdLineParams = in_cmdLineParams;
}
//m_startupProj.ConfigurationManager.ActiveConfiguration.Properties.
// Item("CommandArguments").Value = cmdLineParams;
string configName = dte.Solution.SolutionBuild.ActiveConfiguration.Name;
Microsoft.VisualStudio.VCProjectEngine.VCProject vcProj =
(Microsoft.VisualStudio.VCProjectEngine.VCProject)m_startupProj.Object;
Microsoft.VisualStudio.VCProjectEngine.IVCCollection configs =
(Microsoft.VisualStudio.VCProjectEngine.IVCCollection)vcProj.Configurations;
Microsoft.VisualStudio.VCProjectEngine.VCConfiguration config =
FindConfig(configs, configName, m_platFormStartupProject);
Microsoft.VisualStudio.VCProjectEngine.VCDebugSettings dbgSettings =
(Microsoft.VisualStudio.VCProjectEngine.VCDebugSettings)config.DebugSettings;
dbgSettings.CommandArguments = cmdLineParams;
WriteLine(2, "StartDebugging: now starting debugger with dbgSettings.CommandArguments=" + dbgSettings.CommandArguments);
dte.Debugger.Go(false /* do not wait for end of debugging*/);
}
}
catch (Exception ex)
{
WriteLine(1, "Could not start debugging\nEXCEPTION: " + ex.ToString());
}
}