在C#中附加调试器到另一个进程

22
我希望能自动附加调试器,类似于:System.Diagnostics.Debugger.Launch(),但不是连接到当前进程,而是连接到另一个指定的进程。我有进程名称和PID以标识另一个进程。这有可能吗?

2
点击这里查看https://dev59.com/1nRC5IYBdhLWcg3wD8xV - GSerjo
@GSerjo是一个Visual Studio宏吗?DTE是什么? - Dave Hillier
1
是的,这是宏。DTE对象代表Visual Studio集成开发环境(IDE),是自动化模型层次结构中最高级别的对象。 - GSerjo
5个回答

30

编辑:GSerjo提供了正确的解决方案。我想分享一些关于如何改善它(以及解释)的想法。我希望我的改进答案对那些遇到同样问题的人有用。


将VS调试器附加到进程

手动方式

  1. 打开Windows任务管理器(Ctrl+Shift+Esc)。
  2. 转到选项卡进程
  3. 右键单击进程。
  4. 选择调试

或者,在Visual Studio中,选择调试>附加到进程...

结果因您是否有访问源代码而异。

使用C#自动化

注意:以下代码易错,因为某些值(例如Visual Studio版本号)是硬编码的。如果您计划分发程序,请记住这一点。

首先,将EnvDTE引用添加到项目中(在解决方案资源管理器中右键单击引用文件夹,添加引用)。在下面的代码中,我只展示了不寻常的using指令;正常的指令,如using System被省略了。

因为你正在与COM交互, 所以你需要确保在你的Main方法(应用程序的入口点)上使用STAThreadAttribute进行修饰。

接下来,你需要定义IOleMessageFilter接口, 这将允许你与已定义的COM方法进行交互(请注意ComImportAttribute)。我们需要访问消息过滤器,以便在Visual Studio COM组件阻止我们的某些调用时重试。

using System.Runtime.InteropServices;

[ComImport, Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
public interface IOleMessageFilter
{
    [PreserveSig]
    int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);

    [PreserveSig]
    int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);

    [PreserveSig]
    int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);
}

现在,我们需要实现这个接口来处理传入的消息:
public class MessageFilter : IOleMessageFilter
{
    private const int Handled = 0, RetryAllowed = 2, Retry = 99, Cancel = -1, WaitAndDispatch = 2;

    int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo)
    {
        return Handled;
    }

    int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
    {
        return dwRejectType == RetryAllowed ? Retry : Cancel;
    }

    int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
    {
        return WaitAndDispatch;
    }

    public static void Register()
    {
        CoRegisterMessageFilter(new MessageFilter());
    }

    public static void Revoke()
    {
        CoRegisterMessageFilter(null);
    }

    private static void CoRegisterMessageFilter(IOleMessageFilter newFilter)
    {
        IOleMessageFilter oldFilter;
        CoRegisterMessageFilter(newFilter, out oldFilter);
    }

    [DllImport("Ole32.dll")]
    private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
}

我将返回值定义为常量,以提高可读性,并对整个代码进行了重构,以消除MSDN示例中的一些重复内容,因此希望您会发现它自解释。 extern int CoRegisterMessageFilter 是我们与非托管消息过滤器代码的连接 - 您可以在MSDN上阅读有关 extern 关键字的信息
现在只剩下一些代码来说明用法:
using System.Runtime.InteropServices;
using EnvDTE;

[STAThread]
public static void Main()
{
    MessageFilter.Register();
    var process = GetProcess(7532);
    if (process != null)
    {
        process.Attach();
        Console.WriteLine("Attached to {0}", process.Name);
    }
    MessageFilter.Revoke();
    Console.ReadLine();
}

private static Process GetProcess(int processID)
{
    var dte = (DTE)Marshal.GetActiveObject("VisualStudio.DTE.10.0");
    var processes = dte.Debugger.LocalProcesses.OfType<Process>();
    return processes.SingleOrDefault(x => x.ProcessID == processID);
}

@DaveHillier我又尝试了一次,现在它对我有效了。我顺便添加了一个说明,虽然你可能已经在此期间弄明白了。 - Adam
1
非常好用 - 将 ProgId 升级到 11 以适配 VS2012。 - Dave Hillier
这可以与Process.Start()和Debugger.IsAttached在生成进程中一起使用!做得好。 - vincent gravitas
如果我调试调试附件阶段,它可以工作。 但是,如果我尝试运行直到我放置断点,我会得到RPC_E_CALL_REJECTED 0x80010001。 - Skary
@codesparkle 好用极了!!谢谢。因为详细的回答,给你加1分。 - vendettamit
显示剩余5条评论

13

看看这个

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using EnvDTE;
using NUnit.Framework;

namespace UnitTests
{
    [TestFixture]
    public class ForTest
    {
        [STAThread]  
        [Test]
        public void Test()
        {
            var dte = (DTE) Marshal.GetActiveObject("VisualStudio.DTE.10.0");
            MessageFilter.Register();

            IEnumerable<Process> processes = dte.Debugger.LocalProcesses.OfType<Process>();
            var process = processes.SingleOrDefault(x => x.ProcessID == 6152);
            if (process != null)
            {
                process.Attach();
            }
        }
    }

    public class MessageFilter : IOleMessageFilter
    {
        //
        // Class containing the IOleMessageFilter
        // thread error-handling functions.

        // Start the filter.

        //
        // IOleMessageFilter functions.
        // Handle incoming thread requests.

        #region IOleMessageFilter Members

        int IOleMessageFilter.HandleInComingCall(int dwCallType,
                                                 IntPtr hTaskCaller, int dwTickCount, IntPtr
                                                                                          lpInterfaceInfo)
        {
            //Return the flag SERVERCALL_ISHANDLED.
            return 0;
        }

        // Thread call was rejected, so try again.
        int IOleMessageFilter.RetryRejectedCall(IntPtr
                                                    hTaskCallee, int dwTickCount, int dwRejectType)
        {
            if (dwRejectType == 2)
            // flag = SERVERCALL_RETRYLATER.
            {
                // Retry the thread call immediately if return >=0 & 
                // <100.
                return 99;
            }
            // Too busy; cancel call.
            return -1;
        }

        int IOleMessageFilter.MessagePending(IntPtr hTaskCallee,
                                             int dwTickCount, int dwPendingType)
        {
            //Return the flag PENDINGMSG_WAITDEFPROCESS.
            return 2;
        }

        #endregion

        public static void Register()
        {
            IOleMessageFilter newFilter = new MessageFilter();
            IOleMessageFilter oldFilter = null;
            CoRegisterMessageFilter(newFilter, out oldFilter);
        }

        // Done with the filter, close it.
        public static void Revoke()
        {
            IOleMessageFilter oldFilter = null;
            CoRegisterMessageFilter(null, out oldFilter);
        }

        // Implement the IOleMessageFilter interface.
        [DllImport("Ole32.dll")]
        private static extern int
            CoRegisterMessageFilter(IOleMessageFilter newFilter, out
                                                                     IOleMessageFilter oldFilter);
    }

    [ComImport, Guid("00000016-0000-0000-C000-000000000046"),
     InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
    internal interface IOleMessageFilter
    {
        [PreserveSig]
        int HandleInComingCall(
            int dwCallType,
            IntPtr hTaskCaller,
            int dwTickCount,
            IntPtr lpInterfaceInfo);

        [PreserveSig]
        int RetryRejectedCall(
            IntPtr hTaskCallee,
            int dwTickCount,
            int dwRejectType);

        [PreserveSig]
        int MessagePending(
            IntPtr hTaskCallee,
            int dwTickCount,
            int dwPendingType);
    }
}

2
STAThread Attribute 在除了入口点(Main)之外的所有方法上都是无用的 - Adam
兄弟,你在哪里呀 :) - Emad
@Emad,无论我在哪里,你总是在这里 :) - GSerjo

9
更简单的方法。
public static void Attach(DTE2 dte)
        {
            var processes = dte.Debugger.LocalProcesses;
            foreach (var proc in processes.Cast<EnvDTE.Process>().Where(proc => proc.Name.IndexOf("YourProcess.exe") != -1))
                proc.Attach();
        }

        internal static DTE2 GetCurrent()
        {
            var dte2 = (DTE2)Marshal.GetActiveObject("VisualStudio.DTE.12.0"); // For VisualStudio 2013

            return dte2;
        }

使用方法:

Attach(GetCurrent());

你从哪里获取DTE2和EnvDTE的定义? - user626528
你需要添加对EnvDTE80的引用(引用>添加引用>程序集>框架),然后在你的类中使用它。using EnvDTE80; - Alpesh
显然,它需要EnvDTE80和EnvDTE两者,并且它们位于引用>添加引用>程序集>扩展中(至少在VS2017中)。不确定要使用哪个版本号,因为“VisualStudio.DTE.14.0”会导致“无效的类字符串”异常。 - user626528
1
我正在使用VS2013,所以无法帮助你,但这篇文章可能会有所帮助。祝好运。 - Alpesh
所以它是15.0,而不是14.0。像魔法一样运行。 - user626528
到目前为止我遇到的一个问题是:Marshal.GetActiveObject 返回的是第一个打开的 VS 实例的引用,而不是 active 的实例。 - user626528

4

一种选择是运行:vsjitdebugger.exe -p ProcessId

可能可以使用Process.Start在C#应用程序内实现此操作。


这将运行一个新的 Visual Studio 实例。 - Mike de Klerk
您可以选择现有的实例或使用新实例。 - Dave Hillier

1
如果您遇到无法手动附加调试器到进程的问题,因为进程太快了,不要忘记有时可以在代码的第一行添加Console.ReadKey();,然后您就有足够的时间手动附加它。令人惊讶的是,我花了很长时间才弄清楚这个问题:D

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接