如何在不通过 F5 启动的 CMD 窗口中运行的控制台应用程序上“附加到进程”?我之所以问这个问题是因为此应用程序需要使用命令行参数,我想要一个真实的体验。
我甚至尝试过附加到 CMD.exe
,但没有成功,或者使用 Console.ReadKey()
来设置断点也没有成功。我有些无助了。
这是否可能?
如何在不通过 F5 启动的 CMD 窗口中运行的控制台应用程序上“附加到进程”?我之所以问这个问题是因为此应用程序需要使用命令行参数,我想要一个真实的体验。
我甚至尝试过附加到 CMD.exe
,但没有成功,或者使用 Console.ReadKey()
来设置断点也没有成功。我有些无助了。
这是否可能?
Debugger.Launch()
或Debugger.Break()
放入你的代码中 - 执行此操作后,系统将启动对话框,询问你选择用于调试的哪个 Visual Studio 实例(你可以选择已经打开你的项目的那个)。如果想要从命令行调试而不是使用VS GUI迷宫:
启动Visual Studio命令提示符
输入vsjitdebugger/?,将会给出如下的命令示例:
c:> vsjitdebugger [AppName] [Args] : 启动指定的可执行文件并连接到调试器
c:> tasklist | find /i "web"
当然可以。您可以尝试以下两种方法:
2020更新:@VladV的回答已经过时。
Debugger.Break()
不再起作用。
尝试使用Debugger.Launch()
替代它,还要在这行代码之后设置断点,否则VS会开始抱怨。
正如其他人所说,您可以在项目内指定启动命令行参数,并在Visual Studio中开始调试。
如果您仍然想要附加到正在运行的应用程序,则需要将调试器附加到MyApp.exe(无论您的应用程序叫什么 - 编译为bin \ debug目录的exe文件),而不是cmd.exe。附加到cmd.exe是附加到命令进程,而不是您的应用程序进程。
我本以为我会在这里找到更好的解决方案,但似乎我已经拥有的是最好的。 Debugger.Break()
对我根本不起作用。但是前段时间我在GitHub上找到了VisualStudioAttacher类。现在无法找到该库,但我将发布我的稍微修改过的版本。
您将像这样使用它。
class Program {
static void Main(string[] args) {
VSAttacher.attachDebugger("SolutionFileContainingThisCode.sln");
Console.WriteLine("Hello World"); //set a brakepoint here
//...
}
}
这将仅附加到当前打开的 Visual Studio 实例,无需选择调试器。
代码:
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
}
VSProcessName
和 VSObjectName
(此仅在 Express 和 Community 版本中进行了测试)。