Ответ 1
В строгом смысле, вы вынуждены использовать командную строку для запуска программы с перенаправленным выходом. В противном случае вам нужно будет самостоятельно проанализировать командную строку, оболочка графического интерфейса может не сделать этого.
Если вы просто хотите перенаправить вывод, когда вы Start Debugging
, затем снимите флажок Enable the Visual Studio hosting process
, все готово.
Если вы этого не сделали, и "output.txt"
, который вы видели там, на самом деле, это не, сгенерированное вашим приложением, но "YourApplication.vshost.exe"
, которое порождается до того, как вы начали отлаживать, Visual Studio IDE. Содержимое всегда будет пустым и не может быть написано; потому что он заблокирован Хостинг.
Однако, если вы хотите, чтобы приложение работало так же, как и в том режиме, в котором вы его запускаете, все сложнее.
Когда вы начинаете отладку с приложением, он начинается с:
"YourApplication.exe" arg1 arg2
потому что выход уже перенаправлен IDE.
И когда вы Start Without Debugging
, он начинался с:
"% comspec%" /c "YourApplication.exe" arg1 arg2 ^ > output.txt и приостановка"
Это правильный способ предоставить вашему приложению все аргументы, которые вы указали.
Возможно, вам стоит взглянуть на мой предыдущий ответ Как определить, если "Нажмите любую клавишу, чтобы продолжить.,." будет отображаться?.
Здесь я использую такой подход, как атавистический возврат в коде ниже:
-
Код приложения
using System.Diagnostics; using System.Linq; using System; class Test { public static void Main(string[] args) { foreach(var arg in args) Console.WriteLine(arg); } static Test() { var current=Process.GetCurrentProcess(); var parent=current.GetParentProcess(); var grand=parent.GetParentProcess(); if(null==grand ||grand.MainModule.FileName!=current.MainModule.FileName) using(var child=Process.Start( new ProcessStartInfo { FileName=Environment.GetEnvironmentVariable("comspec"), Arguments="/c\x20"+Environment.CommandLine, RedirectStandardOutput=true, UseShellExecute=false })) { Console.Write(child.StandardOutput.ReadToEnd()); child.WaitForExit(); Environment.Exit(child.ExitCode); } #if false // change to true if child process debugging is needed else { if(!Debugger.IsAttached) Debugger.Launch(); Main(Environment.GetCommandLineArgs().Skip(1).ToArray()); current.Kill(); // or Environment.Exit(0); } #endif } }
Нам также нужен следующий код, чтобы он мог работать:
-
Код методов расширения
using System.Management; // add reference is required using System.Runtime.InteropServices; using System.Diagnostics; using System.Collections.Generic; using System.Linq; using System; public static partial class NativeMethods { [DllImport("kernel32.dll")] public static extern bool TerminateThread( IntPtr hThread, uint dwExitCode); [DllImport("kernel32.dll")] public static extern IntPtr OpenThread( uint dwDesiredAccess, bool bInheritHandle, uint dwThreadId); } public static partial class ProcessThreadExtensions /* public methods */ { public static void Abort(this ProcessThread t) { NativeMethods.TerminateThread( NativeMethods.OpenThread(1, false, (uint)t.Id), 1); } public static IEnumerable<Process> GetChildProcesses(this Process p) { return p.GetProcesses(1); } public static Process GetParentProcess(this Process p) { return p.GetProcesses(-1).SingleOrDefault(); } } partial class ProcessThreadExtensions /* non-public methods */ { static IEnumerable<Process> GetProcesses( this Process p, int direction) { return from format in new[] { "select {0} from Win32_Process where {1}" } let selectName=direction<0?"ParentProcessId":"ProcessId" let filterName=direction<0?"ProcessId":"ParentProcessId" let filter=String.Format("{0} = {1}", p.Id, filterName) let query=String.Format(format, selectName, filter) let searcher=new ManagementObjectSearcher("root\\CIMV2", query) from ManagementObject x in searcher.Get() let process= ProcessThreadExtensions.GetProcessById(x[selectName]) where null!=process select process; } // not a good practice to use generics like this; // but for the convenience .. static Process GetProcessById<T>(T processId) { try { var id=(int)Convert.ChangeType(processId, typeof(int)); return Process.GetProcessById(id); } catch(ArgumentException) { return default(Process); } } }
Поскольку родительский элемент был бы IDE Visual Studio (в настоящее время называется "devenv"
) при отладке. Родительский и дедушканый процесс на самом деле различны, и нам нужно правило, чтобы выполнить некоторую проверку.
Сложная часть заключается в том, что внук действительно попадает в Main
. Код проверяет процесс дедушки и бабушки каждый раз, когда он запускается. Если бабушка и дедушка были null
, тогда она появляется, но порожденный процесс будет %comspec%
, который также является родителем нового процесса, который он начнет с того же исполняемого файла текущего. Таким образом, если бабушка и дедушка совпадают с самим собой, то она не будет продолжать появляться, просто пробегает Main
.
Static Constructor используется в коде, который запускается до Main
. Ответ на вопрос SO: Как работает статический конструктор?.
Когда мы начинаем отладку, мы отлаживаем процесс дедушки и бабушки (который появляется). Для отладки с процессом внука я сделал Debugger.Launch
с условной компиляцией, которая будет вызывать Main
для сохранения Main
clear.
Ответ на вопрос об отладчике также будет полезен: Прикрепить отладчик в С# к другому процессу.