我能否以编程方式将 运行 .NET 6 进程附加到 Visual Studio 调试器的 运行 实例?
Can I programmatically attach a running .NET 6 process to a running instance of Visual Studio debugger?
我正在调试 IPC 逻辑,其中一个 .NET 6 进程正在启动另一个进程,但我无法在同一个 Visual Studio 2022 实例中同时调试这两个进程。
当子进程命中 DebugBreak(true)
如下:
[Conditional("ATTACH_DEBUGGER")]
public static void DebugBreak(bool condition)
{
if (condition)
{
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Launch();
System.Diagnostics.Debugger.Break();
}
}
}
我被提示启动一个新的 VS2022 实例来调试它:
但是,如果我已经在调试父进程,我想在现有 VS 实例中调试它。
这可能吗?是否有我可能缺少的 VS 配置设置?
当前解决方法:手动附加,同时使用下面的 WaitForDebugger()
将子进程置于等待状态:
[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);
}
}
我有一个非常相似的设置,其中以下步骤实现了这一点。但是,这是与 .NET Framework 一起使用的,所以我不能保证它可以与 .NET Core 一起使用。 (更新:请参阅下面的 .NET Core 方法; 请参阅 link 的结尾以在 GitHub 上完成代码示例。)
首先,在您的 first 进程(例如,FirstProcess
)中确定要将调试器附加到 second 进程 (SecondProcess
)。显然 SecondProcess
此时需要 运行(例如 SecondProcess.exe
)。
打开解决方案资源管理器并导航到 FirstProcess
中相关项目的 References
。 Right-click,搜索“env”并添加两个引用,EnvDTE (v.8.0.0.0)
和 EnvDTE80 (v.8.0.0.0)
。
将以下方法添加到 FirstProcess
中您要附加的 class 中:
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 现在应该提示您将以下引用添加到您的 class:
using System.Runtime.InteropServices;
using EnvDTE80;
最后,在 FirstProcess
中您希望将调试器附加到 SecondProcess
的位置插入以下行:
Attach(GetCurrent());
如果您设法使它正常工作,请随时根据 .NET Core 环境所需的任何更改来编辑此答案。
更新 - 对于 .NET Core:
.NET Core 有两个问题需要克服:
- DTE 引用在 .NET Core 中不可用。然而,他们可以
作为 NuGet 包添加。 您将需要添加两个 NuGet
包,
envdte
和 envdte80
(均由 Microsoft 提供 2M+
下载),至 FirstProcess
.
- 方法
Marshal.GetActiveObject()
在 .NET 中不可用
核。要解决此问题,您可以从 Microsoft 获取源代码
(here) 并手动添加; 这已经在
下面的代码示例.
以下是 FirstProcess
的完整工作 .NET Core 代码示例。 这会在启动后以编程方式正确附加到 SecondProcess
。
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);
}
}
}
注 1:这适用于 Visual Studio 2019 和 .NET Core 5。我希望这适用于 VS2022 和 .NET Core 6,只需对上面注释的 Visual Studio 版本进行一次更改。
注意 2:您将需要 只有一个 Visual Studio 打开 的实例(尽管如果这不可能,代码可能很容易修复) .
可下载演示
.NET Framework (v4.7.2) 和 .NET Core (v5.0) 的完整工作示例可在 GitHub 此处获得:https://github.com/NeilTalbott/VisualStudioAutoAttacher
我正在调试 IPC 逻辑,其中一个 .NET 6 进程正在启动另一个进程,但我无法在同一个 Visual Studio 2022 实例中同时调试这两个进程。
当子进程命中 DebugBreak(true)
如下:
[Conditional("ATTACH_DEBUGGER")]
public static void DebugBreak(bool condition)
{
if (condition)
{
if (!System.Diagnostics.Debugger.IsAttached)
{
System.Diagnostics.Debugger.Launch();
System.Diagnostics.Debugger.Break();
}
}
}
我被提示启动一个新的 VS2022 实例来调试它:
但是,如果我已经在调试父进程,我想在现有 VS 实例中调试它。
这可能吗?是否有我可能缺少的 VS 配置设置?
当前解决方法:手动附加,同时使用下面的 WaitForDebugger()
将子进程置于等待状态:
[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);
}
}
我有一个非常相似的设置,其中以下步骤实现了这一点。但是,这是与 .NET Framework 一起使用的,所以我不能保证它可以与 .NET Core 一起使用。 (更新:请参阅下面的 .NET Core 方法; 请参阅 link 的结尾以在 GitHub 上完成代码示例。)
首先,在您的 first 进程(例如,FirstProcess
)中确定要将调试器附加到 second 进程 (SecondProcess
)。显然 SecondProcess
此时需要 运行(例如 SecondProcess.exe
)。
打开解决方案资源管理器并导航到 FirstProcess
中相关项目的 References
。 Right-click,搜索“env”并添加两个引用,EnvDTE (v.8.0.0.0)
和 EnvDTE80 (v.8.0.0.0)
。
将以下方法添加到 FirstProcess
中您要附加的 class 中:
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 现在应该提示您将以下引用添加到您的 class:
using System.Runtime.InteropServices;
using EnvDTE80;
最后,在 FirstProcess
中您希望将调试器附加到 SecondProcess
的位置插入以下行:
Attach(GetCurrent());
如果您设法使它正常工作,请随时根据 .NET Core 环境所需的任何更改来编辑此答案。
更新 - 对于 .NET Core:
.NET Core 有两个问题需要克服:
- DTE 引用在 .NET Core 中不可用。然而,他们可以
作为 NuGet 包添加。 您将需要添加两个 NuGet
包,
envdte
和envdte80
(均由 Microsoft 提供 2M+ 下载),至FirstProcess
. - 方法
Marshal.GetActiveObject()
在 .NET 中不可用 核。要解决此问题,您可以从 Microsoft 获取源代码 (here) 并手动添加; 这已经在 下面的代码示例.
以下是 FirstProcess
的完整工作 .NET Core 代码示例。 这会在启动后以编程方式正确附加到 SecondProcess
。
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);
}
}
}
注 1:这适用于 Visual Studio 2019 和 .NET Core 5。我希望这适用于 VS2022 和 .NET Core 6,只需对上面注释的 Visual Studio 版本进行一次更改。
注意 2:您将需要 只有一个 Visual Studio 打开 的实例(尽管如果这不可能,代码可能很容易修复) .
可下载演示
.NET Framework (v4.7.2) 和 .NET Core (v5.0) 的完整工作示例可在 GitHub 此处获得:https://github.com/NeilTalbott/VisualStudioAutoAttacher