Last active
July 25, 2024 09:56
-
-
Save atruskie/3813175 to your computer and use it in GitHub Desktop.
A class for programmatically attaching Visual Studio to debug a process. It can be used to automatically attach Visual Studio to your process. It can dynamically attach any solution to any process.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// -------------------------------------------------------------------------------------------------------------------- | |
// <copyright file="AutoAttachVs.cs" company="QutEcoacoustics"> | |
// All code in this file and all associated files are the copyright and property of the QUT Ecoacoustics Research Group (formerly MQUTeR, and formerly QUT Bioacoustics Research Group). | |
// </copyright> | |
// <summary> | |
// Example taken from this gist. | |
// </summary> | |
// -------------------------------------------------------------------------------------------------------------------- | |
#if DEBUG | |
namespace Acoustics.Shared.Debugging | |
{ | |
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics.CodeAnalysis; | |
using System.IO; | |
using System.Linq; | |
using System.Runtime.InteropServices; | |
using System.Runtime.InteropServices.ComTypes; | |
using EnvDTE; | |
using DTEProcess = EnvDTE.Process; | |
using Process = System.Diagnostics.Process; | |
/// <summary> | |
/// Example taken from <a href="https://gist.github.com/3813175">this gist</a>. | |
/// </summary> | |
[SuppressMessage("StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", | |
Justification = "Reviewed. Suppression is OK here.", Scope = "class")] | |
public static class VisualStudioAttacher | |
{ | |
[DllImport("ole32.dll")] | |
public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc); | |
[DllImport("ole32.dll")] | |
public static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot); | |
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
public static extern bool SetForegroundWindow(IntPtr hWnd); | |
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] | |
public static extern IntPtr SetFocus(IntPtr hWnd); | |
public static string GetSolutionForVisualStudio(Process visualStudioProcess) | |
{ | |
_DTE visualStudioInstance; | |
if (TryGetVsInstance(visualStudioProcess.Id, out visualStudioInstance)) | |
{ | |
try | |
{ | |
return visualStudioInstance.Solution.FullName; | |
} | |
catch (Exception) | |
{ | |
} | |
} | |
return null; | |
} | |
public static Process GetAttachedVisualStudio(Process applicationProcess) | |
{ | |
IEnumerable<Process> visualStudios = GetVisualStudioProcesses(); | |
foreach (Process visualStudio in visualStudios) | |
{ | |
_DTE visualStudioInstance; | |
if (TryGetVsInstance(visualStudio.Id, out visualStudioInstance)) | |
{ | |
try | |
{ | |
foreach (Process debuggedProcess in visualStudioInstance.Debugger.DebuggedProcesses) | |
{ | |
if (debuggedProcess.Id == applicationProcess.Id) | |
{ | |
return debuggedProcess; | |
} | |
} | |
} | |
catch (Exception) | |
{ | |
} | |
} | |
} | |
return null; | |
} | |
/// <summary> | |
/// The method to use to attach visual studio to a specified process. | |
/// </summary> | |
/// <param name="visualStudioProcess"> | |
/// The visual studio process to attach to. | |
/// </param> | |
/// <param name="applicationProcess"> | |
/// The application process that needs to be debugged. | |
/// </param> | |
/// <exception cref="InvalidOperationException"> | |
/// Thrown when the application process is null. | |
/// </exception> | |
public static void AttachVisualStudioToProcess(Process visualStudioProcess, Process applicationProcess) | |
{ | |
_DTE visualStudioInstance; | |
if (TryGetVsInstance(visualStudioProcess.Id, out visualStudioInstance)) | |
{ | |
// Find the process you want the VS instance to attach to... | |
DTEProcess processToAttachTo = | |
visualStudioInstance.Debugger.LocalProcesses.Cast<DTEProcess>() | |
.FirstOrDefault(process => process.ProcessID == applicationProcess.Id); | |
// Attach to the process. | |
if (processToAttachTo != null) | |
{ | |
processToAttachTo.Attach(); | |
ShowWindow((int)visualStudioProcess.MainWindowHandle, 3); | |
SetForegroundWindow(visualStudioProcess.MainWindowHandle); | |
} | |
else | |
{ | |
throw new InvalidOperationException( | |
"Visual Studio process cannot find specified application '" + applicationProcess.Id + "'"); | |
} | |
} | |
} | |
/// <summary> | |
/// The get visual studio for solutions. | |
/// </summary> | |
/// <param name="solutionNames"> | |
/// The solution names. | |
/// </param> | |
/// <returns> | |
/// The <see cref="Process"/>. | |
/// </returns> | |
public static Process GetVisualStudioForSolutions(List<string> solutionNames) | |
{ | |
foreach (string solution in solutionNames) | |
{ | |
Process visualStudioForSolution = GetVisualStudioForSolution(solution); | |
if (visualStudioForSolution != null) | |
{ | |
return visualStudioForSolution; | |
} | |
} | |
return null; | |
} | |
/// <summary> | |
/// The get visual studio process that is running and has the specified solution loaded. | |
/// </summary> | |
/// <param name="solutionName"> | |
/// The solution name to look for. | |
/// </param> | |
/// <returns> | |
/// The visual studio <see cref="Process"/> with the specified solution name. | |
/// </returns> | |
public static Process GetVisualStudioForSolution(string solutionName) | |
{ | |
IEnumerable<Process> visualStudios = GetVisualStudioProcesses(); | |
foreach (Process visualStudio in visualStudios) | |
{ | |
_DTE visualStudioInstance; | |
if (TryGetVsInstance(visualStudio.Id, out visualStudioInstance)) | |
{ | |
try | |
{ | |
string actualSolutionName = Path.GetFileName(visualStudioInstance.Solution.FullName); | |
if (string.Compare( | |
actualSolutionName, | |
solutionName, | |
StringComparison.InvariantCultureIgnoreCase) == 0) | |
{ | |
return visualStudio; | |
} | |
} | |
catch (Exception) | |
{ | |
throw; | |
} | |
} | |
} | |
return null; | |
} | |
[DllImport("User32")] | |
private static extern int ShowWindow(int hwnd, int nCmdShow); | |
private static IEnumerable<Process> GetVisualStudioProcesses() | |
{ | |
Process[] processes = Process.GetProcesses(); | |
return processes.Where(o => o.ProcessName.Contains("devenv")); | |
} | |
private static bool TryGetVsInstance(int processId, out _DTE instance) | |
{ | |
IntPtr numFetched = IntPtr.Zero; | |
IRunningObjectTable runningObjectTable; | |
IEnumMoniker monikerEnumerator; | |
IMoniker[] monikers = new IMoniker[1]; | |
GetRunningObjectTable(0, out runningObjectTable); | |
runningObjectTable.EnumRunning(out monikerEnumerator); | |
monikerEnumerator.Reset(); | |
while (monikerEnumerator.Next(1, monikers, numFetched) == 0) | |
{ | |
IBindCtx ctx; | |
CreateBindCtx(0, out ctx); | |
string runningObjectName; | |
monikers[0].GetDisplayName(ctx, null, out runningObjectName); | |
object runningObjectVal; | |
runningObjectTable.GetObject(monikers[0], out runningObjectVal); | |
if (runningObjectVal is _DTE && runningObjectName.StartsWith("!VisualStudio")) | |
{ | |
int currentProcessId = int.Parse(runningObjectName.Split(':')[1]); | |
if (currentProcessId == processId) | |
{ | |
instance = (_DTE)runningObjectVal; | |
return true; | |
} | |
} | |
} | |
instance = null; | |
return false; | |
} | |
} | |
} | |
#endif |
I've found that I need to add this in order to compile this code.
<Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
<Reference Include="envdte80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<EmbedInteropTypes>True</EmbedInteropTypes>
</Reference>
Source: https://reviews.llvm.org/D31740
That's a good point. It depends on the version of Visual Studio you are using. My references (riddled with tech debt) include:
<Reference Include="envdte, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte.dll</HintPath>
</Reference>
<Reference Include="envdte100, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte100.dll</HintPath>
</Reference>
<Reference Include="envdte80, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte80.dll</HintPath>
</Reference>
<Reference Include="envdte90, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte90.dll</HintPath>
</Reference>
<Reference Include="envdte90a, Version=9.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<SpecificVersion>False</SpecificVersion>
<EmbedInteropTypes>True</EmbedInteropTypes>
<HintPath>..\..\..\..\..\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\Common7\IDE\PublicAssemblies\envdte90a.dll</HintPath>
</Reference>
@atruskie: Because you are obviously mostly the only one doing the same things as I do, I have to ask you one question about the performance. It seems to me that locating the LocalProcesses takes a very lon time on my machine. Did you experience the same problem?
@msedi It has been a long time since I've used or needed this solution. I don't remember it being slow though.
@atruskie: No problem. I was just asking ;-)
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@aakasheth95, I've updated the code so that it works with a newer Visual Studio API.
You can see an example usage here: https://github.com/QutEcoacoustics/audio-analysis/blob/089e1489f209b09ca60de7b4a516a9ae70518c51/src/AnalysisPrograms/Production/MainEntryUtilities.cs#L140-L184
Note: this is not a plugin. This code is embedded into an executable. When the executable runs, it loads a visual studio DLL and starts a debugging process on itself.
There are few occasions for which this code is appropriate.