繁体   English   中英

我能否以编程方式将正在运行的 .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 2022 附加调试器

但是,如果我已经在调试父进程,我想在现有的 VS 实例中调试它。

这可能吗? 是否有我可能缺少的 VS 配置设置?

当前解决方法:手动附加,同时使用下面的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);
        }
    }

我有一个非常相似的设置,其中以下步骤实现了这一点。 但是,这是.NET 框架,所以我不能保证它能与 .NET 核心一起使用。 (更新:.NET 核心方法见下文;GitHub上完整代码示例的链接见末尾。)

首先,在您的第一个进程(例如FirstProcess )中确定一个点,您希望将调试器附加到第二个进程( SecondProcess )。 显然, SecondProcess需要运行(例如SecondProcess.exe )。

打开 Solution Explorer 并导航到FirstProcess中相关项目的References 右键单击,搜索“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 核心环境所需的任何更改进行修改。

更新 - 对于 .NET 核心:

对于 .NET 核心有两个问题需要克服:

  1. DTE 参考在 .NET Core 中不可用。 但是,它们可以作为 NuGet 包添加。 您需要将两个 NuGet 包envdteenvdte80 (均由 Microsoft 提供,下载量超过 200 万)添加到FirstProcess
  2. Marshal.GetActiveObject()方法在 .NET Core 中不可用。 要解决此问题,您可以从 Microsoft( 此处)获取源代码并手动添加; 这已在下面的代码示例中完成

以下是FirstProcess的完整工作 .NET 核心代码示例。 这在启动后以编程方式正确附加到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

暂无
暂无

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

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