简体   繁体   English

我能否以编程方式将正在运行的 .NET 6 进程附加到正在运行的 Visual Studio 调试器实例?

[英]Can I programmatically attach a running .NET 6 process to a running instance of Visual Studio debugger?

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.我正在调试一个 IPC 逻辑,其中一个 .NET 6 个进程正在启动另一个进程,但我无法在同一个 Visual Studio 2022 实例中同时调试这两个进程。

When the child process hits DebugBreak(true) as below:当子进程命中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();
            }
        }
    }

I get prompted to launch a new instance of VS2022 to debug it:我被提示启动一个新的 VS2022 实例来调试它:

VS 2022 附加调试器

However, I want to debug it within the existing VS instance, were I'm already debugging the parent process.但是,如果我已经在调试父进程,我想在现有的 VS 实例中调试它。

Is that possible at all?这可能吗? Is there a VS config setting I might be missing?是否有我可能缺少的 VS 配置设置?

Current workaround: attaching manually, while putting the child process in the waiting state using WaitForDebugger() below:当前解决方法:手动附加,同时使用下面的WaitForDebugger()将子进程置于等待 state 中:

    [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.但是,这是.NET 框架,所以我不能保证它能与 .NET 核心一起使用。 (UPDATE: see below for .NET Core approach; see end for link to complete code samples on GitHub.) (更新:.NET 核心方法见下文;GitHub上完整代码示例的链接见末尾。)

First, identify a point in your first process (say, FirstProcess ) where you want to attach the debugger to the second process ( SecondProcess ).首先,在您的第一个进程(例如FirstProcess )中确定一个点,您希望将调试器附加到第二个进程( SecondProcess )。 Obviously SecondProcess will need to be running by this point (as SecondProcess.exe , say).显然, SecondProcess需要运行(例如SecondProcess.exe )。

Open Solution Explorer and navigate to References for the relevant project in FirstProcess .打开 Solution Explorer 并导航到FirstProcess中相关项目的References Right-click, search for "env" and add two references, EnvDTE (v.8.0.0.0) and EnvDTE80 (v.8.0.0.0) .右键单击,搜索“env”并添加两个引用, EnvDTE (v.8.0.0.0)EnvDTE80 (v.8.0.0.0)

Add the following methods to the class in FirstProcess from where you want to attach:从要附加的位置将以下方法添加到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 should now prompt you to add the following references to your class: Visual Studio 现在应提示您将以下引用添加到您的 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 :最后,在FirstProcess中您希望将调试器附加到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.如果您设法使它正常工作,请随时编辑此答案,并根据 .NET 核心环境所需的任何更改进行修改。

UPDATE - for .NET Core:更新 - 对于 .NET 核心:

For .NET Core there are two problems to overcome:对于 .NET 核心有两个问题需要克服:

  1. The DTE references are not available in .NET Core. DTE 参考在 .NET Core 中不可用。 However, they can be added as NuGet packages.但是,它们可以作为 NuGet 包添加。 You will need to add two NuGet packages, envdte and envdte80 (both by Microsoft with 2M+ downloads), to FirstProcess .您需要将两个 NuGet 包envdteenvdte80 (均由 Microsoft 提供,下载量超过 200 万)添加到FirstProcess
  2. The method Marshal.GetActiveObject() is not available in .NET Core. Marshal.GetActiveObject()方法在 .NET Core 中不可用。 To resolve this you can get the source code from Microsoft ( here ) and add it manually;要解决此问题,您可以从 Microsoft( 此处)获取源代码并手动添加; this has already been done in the code sample below .这已在下面的代码示例中完成

What follows is a complete working .NET Core code sample for FirstProcess .以下是FirstProcess的完整工作 .NET 核心代码示例。 This correctly attaches programmatically to SecondProcess after startup.这在启动后以编程方式正确附加到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);

        }
    }
}

Note 1: This is for Visual Studio 2019 and .NET Core 5 .注 1:这是针对Visual Studio 2019 和 .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.我希望这适用于 VS2022 和 .NET Core 6,只需对上面注释的 Visual Studio 版本进行一次更改。

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).注意 2:您将只需要打开一个 Visual Studio 实例(尽管如果这不可能,代码可能很容易修复)。

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 .NET Framework (v4.7.2) 和 .NET Core (v5.0) 的完整工作示例可在 GitHub 此处获得: https://github.com/NeilTalbott/VisualStudioAutoAttacher

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM