C# 调试:附加到在cmd.exe内运行的控制台应用程序的进程

C# 调试:附加到在cmd.exe内运行的控制台应用程序的进程,c#,debugging,console-application,C#,Debugging,Console Application,对于从CMD窗口运行而不是由F5启动的控制台应用程序,如何“附加到进程…”?我问这个问题的原因是因为应用程序需要命令行参数,我希望有一个真正的体验 我甚至附加到CMD.exe,但没有运气,或者使用Console.ReadKey()设置断点也没有运气。我在这里有点不知所措 这可能吗?当然可能。尝试以下两种方法之一: 启动进程,然后转到调试->附加并查找进程。您可能需要刷新才能看到它 如果可能,在代码中添加“Debugger.Break()”语句;这将自动中断(但请确保将其删除或使用预处理器指令将其

对于从CMD窗口运行而不是由F5启动的控制台应用程序,如何“附加到进程…”?我问这个问题的原因是因为应用程序需要命令行参数,我希望有一个真正的体验

我甚至附加到
CMD.exe
,但没有运气,或者使用
Console.ReadKey()
设置断点也没有运气。我在这里有点不知所措


这可能吗?

当然可能。尝试以下两种方法之一:

  • 启动进程,然后转到调试->附加并查找进程。您可能需要刷新才能看到它
  • 如果可能,在代码中添加“Debugger.Break()”语句;这将自动中断(但请确保将其删除或使用预处理器指令将其包围,以便它不会进入生产代码)
  • 在“项目设置”的“调试”部分,有一个“命令行参数:”文本框。当VS调试器启动C#程序时,它会将这些参数传递给进程,就像程序是从命令行启动的一样

    另一种方法是使用命令行调试器。这里有几个选项,但老实说,它们可能不是您想要使用的而不是VS,除非您进入一些非常复杂的调试场景。如果您有兴趣查看它们,下面的答案中有一个很好的总结:


    您还可以尝试在初始化早期调用
    System.Diagnostics.Debugger.Break()
    的技术-如果程序在调试器下运行,它将中断,它不在调试器下运行,则应询问您是否要附加调试器。您可以根据配置文件或环境变量设置有条件地进行调用,因此只有您真正感兴趣时才可以中断调用(有点干扰,但也不太糟糕)。

    正如其他人所说,您可以在项目中指定stratup命令行参数,然后在Visual Studio中开始调试


    如果仍要附加到正在运行的应用程序,则需要将调试器附加到MyApp.exe(无论您的应用程序叫什么-编译到bin\debug目录的exe),而不是cmd.exe。附加到cmd.exe它附加到命令进程,而不是应用程序的进程。

    要从命令行而不是使用VS GUI进行调试,请执行以下操作:

    • 启动Visual Studio命令提示符

    • 键入vsjitdebugger/?,这将提供如下命令示例:

    c:>[AppName][Args]:启动指定的可执行文件并附加到调试器

    • 键入tlisttasklist将为您提供附加到现有流程的PID。例如:
    c:>任务列表|查找/i“web”

    您有一些选择:

    • 在VisualStudio中使用“调试->命令行参数”选项
    • 使用“调试->附加到进程”并找到您的进程;它不是cmd.exe,而是一个具有可执行名称(如“MyProject.exe”)的进程。您可以使用或其他支持“树视图”的任务管理器轻松查找进程ID-只需查找由cmd.exe启动的进程
    • 将Debugger.Break()放入代码中-执行此方法时,系统将启动一个对话框,要求您选择用于调试的Visual Studio实例(您可以选择项目已打开的实例)

    只需在“HKEY\U LOCAL\U MACHINE\Software\Microsoft\Windows NT\currentversion\image file execution options”中为exe的名称添加一个注册表项,并在其下添加一个值为“vsjitdebugger.exe”的“debugger”键,即可看到弹出一个对话框,要求您在exe启动时选择要调试的VS版本


    有关更多信息,请参阅MSDN“”。

    我想我会在这里找到一些更好的解决方案,但似乎我已有的解决方案是最好的
    Debugger.Break()
    根本不适合我。但不久前我在GitHub上发现了VisualStudioAttacher类。现在找不到销售代表,但我正在发布稍微修改过的版本

    你会像这样使用它

    class Program {
        static void Main(string[] args) {
            VSAttacher.attachDebugger("SolutionFileContainingThisCode.sln");
    
            Console.WriteLine("Hello World"); //set a brakepoint here
            //...               
        }
    
    }
    
    这将只附加到当前打开的VisualStudio实例,不需要您选择调试器

    安装程序
  • 创建名为VSAttacher的新类库项目,或任何您喜欢的项目
  • 在要调试的项目中添加对VSAttacher项目的引用
  • VSAttacher项目中,添加对envdte库的引用
  • 将以下代码粘贴到VSAttacher项目:
  • 代码:

    使用System.IO;
    使用EnvDTE;
    使用DTEProcess=EnvDTE.Process;
    使用制度;
    使用System.Collections.Generic;
    使用过程=System.Diagnostics.Process;
    使用System.Linq;
    使用System.Runtime.InteropServices;
    使用System.Runtime.InteropServices.ComTypes;
    命名空间AppController{
    #区域类
    ///VisualStudioAttacher。
    公共静态类vAttacher{
    公共静态操作日志=(o)=>Console.WriteLine(o);
    //根据您的visual studio版本更改以下变量
    //公共静态字符串VSProcessName=“WDExpress”;
    //公共静态字符串VSObjectName=“!WDExpress”;
    公共静态字符串VSProcessName=“devenv”;
    公共静态字符串VSObjectName=“!VisualStudio”;
    /// 
    ///尝试将程序附加到Visual Studio调试器。
    ///如果附加成功,则返回true;如果调试器附加失败,则返回false。
    /// 
    ///包含要调试的代码的解决方案文件。
    公共静态bool attachDebugger(字符串sln){
    if(System.Diagnostics.Debugger.IsAttached)返回true;
    日志(“附加到Visual S
    
    using System.IO;
    using EnvDTE;
    using DTEProcess = EnvDTE.Process;
    using System;
    using System.Collections.Generic;
    using Process = System.Diagnostics.Process;
    using System.Linq;
    using System.Runtime.InteropServices;
    using System.Runtime.InteropServices.ComTypes;
    
    namespace AppController {
        #region Classes
    
        /// <summary>Visual Studio attacher.</summary>
        public static class VSAttacher {
    
            public static Action<object> log = (o) => Console.WriteLine(o);
        
            //Change following variables depending on your version of visual studio
            //public static string VSProcessName = "WDExpress";
            //public static string VSObjectName = "!WDExpress";
            public static string VSProcessName = "devenv";
            public static string VSObjectName = "!VisualStudio";
    
            /// <summary>
            /// Tries to attach the program to Visual Studio debugger.
            /// Returns true is the attaching was successful, false is debugger attaching failed.
            /// </summary>
            /// <param name="sln">Solution file containing code to be debugged.</param>
            public static bool attachDebugger(string sln) {
                if (System.Diagnostics.Debugger.IsAttached) return true;
                log("Attaching to Visual Studio debugger...");
    
                var proc = VSAttacher.GetVisualStudioForSolutions(
                    new List<string>() { Path.GetFileName(sln) });
                if (proc != null) VSAttacher.AttachVSToProcess(
                        proc, Process.GetCurrentProcess());
                else { 
                    try { System.Diagnostics.Debugger.Launch(); }
                    catch (Exception e) { }
                } // try and attach the old fashioned way
    
                if (System.Diagnostics.Debugger.IsAttached) {
                    log(@"The builder was attached successfully. Further messages will displayed in ""Debug"" output of ""Output"" window.");
                    return true;
                }
                log("Could not attach to visual studio instance.");
                return false;
            }
    
            #region Public Methods
    
    
            #region Imports
            [DllImport("User32")]
            private static extern int ShowWindow(int hwnd, int nCmdShow);
    
            /// <summary>Returns a pointer to an implementation of <see cref="IBindCtx"/> (a bind context object). This object stores information about a particular moniker-binding operation.</summary>
            /// <param name="reserved">This parameter is reserved and must be 0.</param>
            /// <param name="ppbc">Address of an <see cref="IBindCtx"/>* pointer variable that receives the interface pointer to the new bind context object. When the function is successful, the caller is responsible for calling Release on the bind context. A NULL value for the bind context indicates that an error occurred.</param>
            /// <returns></returns>
            [DllImport("ole32.dll")]
            public static extern int CreateBindCtx(int reserved, out IBindCtx ppbc);
    
            /// <summary>Returns a pointer to the <see cref="IRunningObjectTable"/> interface on the local running object table (ROT).</summary>
            /// <param name="reserved">This parameter is reserved and must be 0.</param>
            /// <param name="prot">The address of an IRunningObjectTable* pointer variable that receives the interface pointer to the local ROT. When the function is successful, the caller is responsible for calling Release on the interface pointer. If an error occurs, *pprot is undefined.</param>
            /// <returns>his function can return the standard return values E_UNEXPECTED and S_OK.</returns>
            [DllImport("ole32.dll")]
            public static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable prot);
    
    
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern bool SetForegroundWindow(IntPtr hWnd);
            [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern IntPtr SetFocus(IntPtr hWnd);
            #endregion
    
            public static string GetSolutionForVisualStudio(Process visualStudioProcess) {
                var vsi = getVSInstance(visualStudioProcess.Id);
                try { return vsi?.Solution.FullName;}
                catch (Exception) {} return null;
            }
    
            public static Process GetAttachedVisualStudio(Process ap) {
                var vsps = getVSProcess();
                foreach (Process vsp in vsps) {
                    var vsi = getVSInstance(vsp.Id);
                    if (vsi == null) continue;
                    try {
                        foreach (Process dp in vsi.Debugger.DebuggedProcesses)
                            if (dp.Id == ap.Id) return dp;
                    } catch (Exception) {}
                }
                return null;
            }
    
            public static void AttachVSToProcess(Process vsp, Process applicationProcess) {
                var vsi = getVSInstance(vsp.Id);
                if (vsi == null) return;
                //Find the process you want the VS instance to attach to...
                DTEProcess tp = vsi.Debugger.LocalProcesses.Cast<DTEProcess>().FirstOrDefault(process => process.ProcessID == applicationProcess.Id);
    
                //Attach to the process.
                if (tp != null) {
                    tp.Attach();
    
                    ShowWindow((int)vsp.MainWindowHandle, 3);
                    SetForegroundWindow(vsp.MainWindowHandle);
                } else {
                    throw new InvalidOperationException("Visual Studio process cannot find specified application '" + applicationProcess.Id + "'");
                }
            }
    
            public static Process GetVisualStudioForSolutions(List<string> sns) {
                foreach (string sn in sns) {
                    var vsp = GetVSProc(sn);
                    if (vsp != null) return vsp;
                }
                return null;
            }
    
    
            public static Process GetVSProc(string name) {
                var vsps = getVSProcess(); var e = false;
                foreach (Process vsp in vsps) {
                    _DTE vsi = getVSInstance(vsp.Id);
                    if (vsi == null) { e = true; continue; }
                    try {
                        string sn = Path.GetFileName(vsi.Solution.FullName);
                        if (string.Compare(sn, name, StringComparison.InvariantCultureIgnoreCase)
                            == 0) return vsp;
                    } catch (Exception) { e = true; }
                }
                if (!e) log($@"No running Visual Studio process named ""{VSProcessName}"" were found.");
                return null;
            }
    
            #endregion
    
            #region Private Methods
    
            private static IEnumerable<Process> getVSProcess() {
                Process[] ps = Process.GetProcesses();
                //var vsp = ps.Where(p => p.Id == 11576);
                return ps.Where(o => o.ProcessName.Contains(VSProcessName));
            }
    
            private static _DTE getVSInstance(int processId) {
                IntPtr numFetched = IntPtr.Zero;
                IMoniker[] m = new IMoniker[1];
    
                GetRunningObjectTable(0, out var rot);
                rot.EnumRunning(out var ms); ms.Reset();
    
                var rons = new  List<string>();
                while (ms.Next(1, m, numFetched) == 0) {
                    IBindCtx ctx;
                    CreateBindCtx(0, out ctx);
    
                    m[0].GetDisplayName(ctx, null, out var ron);
                    rons.Add(ron);
                    rot.GetObject(m[0], out var rov);
    
                    if (rov is _DTE && ron.StartsWith(VSObjectName)) {
                        int currentProcessId = int.Parse(ron.Split(':')[1]);
    
                        if (currentProcessId == processId) {
                            return (_DTE)rov;
                        }
                    }
                }
                log($@"No Visual Studio _DTE object was found with the name ""{VSObjectName}"" that resides in given process (PID:{processId}).");
                log("The processes exposes following objects:");
                foreach (var ron in rons) log(ron);
                return null;
            }
    
            #endregion
        }
    
        #endregion
    }