I'm debugging an IPC logic where one .NET 6 processes is launching another, and I can't get to debug both within the same Visual Studio 2022 instance.
When the child process hits DebugBreak(true)
as below:
[Conditional("ATTACH_DEBUGGER")]
public static void DebugBreak(bool condition)
{
if (condition)
{
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Launch();
System.Diagnostics.Debugger.Break();
}
}
}
I get prompted to launch a new instance of VS2022 to debug it:
However, I want to debug it within the existing VS instance, were I'm already debugging the parent process.
Is that possible at all? Is there a VS config setting I might be missing?
Current workaround: attaching manually, while putting the child process in the waiting state using WaitForDebugger()
below:
[Conditional("ATTACH_DEBUGGER")]
public static void WaitForDebugger()
{
using var process = Process.GetCurrentProcess();
Console.WriteLine($"Waiting for debugger, process: {process.ProcessName}, id: {process.Id}, Assembly: {Assembly.GetExecutingAssembly().Location}");
while (!System.Diagnostics.Debugger.IsAttached)
{
// hit Esc to continue without debugger
if (Console.KeyAvailable && Console.ReadKey(intercept: true).KeyChar == 27)
{
break;
}
Thread.Sleep(TimeSpan.FromSeconds(1));
Console.Beep(frequency: 1000, duration: 50);
}
}
I have a very similar setup where the following steps achieve this. However, this is with .NET Framework so I cannot guarantee it will work with .NET Core. (UPDATE: see below for .NET Core approach; see end for link to complete code samples on GitHub.)
First, identify a point in your first process (say, FirstProcess
) where you want to attach the debugger to the second process (SecondProcess
). Obviously SecondProcess
will need to be running by this point (as SecondProcess.exe
, say).
Open Solution Explorer and navigate to References
for the relevant project in FirstProcess
. Right-click, search for "env" and add two references, EnvDTE (v.8.0.0.0)
and EnvDTE80 (v.8.0.0.0)
.
Add the following methods to the class in FirstProcess
from where you want to attach:
private static void Attach(DTE2 dte)
{
var processName = "SecondProcess.exe";
var processes = dte.Debugger.LocalProcesses;
// Note: Depending on your setup, consider whether an exact match is required instead of using .IndexOf()
foreach (var proc in processes.Cast<EnvDTE.Process>().Where(proc => proc.Name.IndexOf(processName) != -1))
{
proc.Attach();
}
}
private static DTE2 GetCurrent()
{
// Note: "16.0" is for Visual Studio 2019; you might need to tweak this for VS2022.
var dte2 = (DTE2)Marshal.GetActiveObject("VisualStudio.DTE.16.0");
return dte2;
}
Visual Studio should now prompt you to add the following references to your class:
using System.Runtime.InteropServices;
using EnvDTE80;
Finally, insert the following line at the point in FirstProcess
where you wish to attach the debugger to SecondProcess
:
Attach(GetCurrent());
If you manage to get this working, please feel free to edit this answer with any changes needed for the .NET Core environment.
UPDATE - for .NET Core:
For .NET Core there are two problems to overcome:
envdte
and envdte80
(both by Microsoft with 2M+
downloads), to FirstProcess
.Marshal.GetActiveObject()
is not available in .NET
Core. To resolve this you can get the source code from Microsoft
(here) and add it manually; this has already been done in
the code sample below.What follows is a complete working .NET Core code sample for FirstProcess
. This correctly attaches programmatically to SecondProcess
after startup.
namespace FirstProcess
{
using System;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Threading;
using EnvDTE80;
class Program
{
private const string FileName = "C:\\Users\\YourUserName\\source\\repos\\TwoProcessesSolution\\SecondProcess\\bin\\Debug\\net5.0\\SecondProcess.exe";
private const string ProcessName = "SecondProcess";
static void Main(string[] args)
{
var childProcess = Process.Start(new ProcessStartInfo(FileName));
Attach(GetCurrent());
while (true)
{
// Your code here.
Thread.Sleep(1000);
}
childProcess.Kill();
}
private static void Attach(DTE2 dte)
{
var processes = dte.Debugger.LocalProcesses;
// Note: Depending on your setup, consider whether an exact match is required instead of using .IndexOf()
foreach (var proc in processes.Cast<EnvDTE.Process>().Where(proc => proc.Name.IndexOf(ProcessName) != -1))
{
proc.Attach();
}
}
private static DTE2 GetCurrent()
{
// Note: "16.0" is for Visual Studio 2019; you might need to tweak this for VS2022.
var dte2 = (DTE2)Marshal2.GetActiveObject("VisualStudio.DTE.16.0");
return dte2;
}
public static class Marshal2
{
internal const String OLEAUT32 = "oleaut32.dll";
internal const String OLE32 = "ole32.dll";
[System.Security.SecurityCritical] // auto-generated_required
public static Object GetActiveObject(String progID)
{
Object obj = null;
Guid clsid;
// Call CLSIDFromProgIDEx first then fall back on CLSIDFromProgID if
// CLSIDFromProgIDEx doesn't exist.
try
{
CLSIDFromProgIDEx(progID, out clsid);
}
// catch
catch (Exception)
{
CLSIDFromProgID(progID, out clsid);
}
GetActiveObject(ref clsid, IntPtr.Zero, out obj);
return obj;
}
//[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
[DllImport(OLE32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void CLSIDFromProgIDEx([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);
//[DllImport(Microsoft.Win32.Win32Native.OLE32, PreserveSig = false)]
[DllImport(OLE32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void CLSIDFromProgID([MarshalAs(UnmanagedType.LPWStr)] String progId, out Guid clsid);
//[DllImport(Microsoft.Win32.Win32Native.OLEAUT32, PreserveSig = false)]
[DllImport(OLEAUT32, PreserveSig = false)]
[ResourceExposure(ResourceScope.None)]
[SuppressUnmanagedCodeSecurity]
[System.Security.SecurityCritical] // auto-generated
private static extern void GetActiveObject(ref Guid rclsid, IntPtr reserved, [MarshalAs(UnmanagedType.Interface)] out Object ppunk);
}
}
}
Note 1: This is for Visual Studio 2019 and .NET Core 5. I would expect this to work for VS2022 and .NET Core 6 with a single change to the Visual Studio version as annotated above.
Note 2: You will need to have only one instance of Visual Studio open (though if this isn't possible, the code is probably fixable quite easily).
Downloadable demo
Complete working examples for both .NET Framework (v4.7.2) and .NET Core (v5.0) are available on GitHub here: https://github.com/NeilTalbott/VisualStudioAutoAttacher