21

我正在尝试将 C# 程序的输出重定向到文件。使用“cmd.exe”时,我可以简单地运行它myprogram.exe arg1 arg2 > out.txt,但我想使用 Visual Studio Start Options来完成同样的事情。

我创建了一个C# 空项目并添加了以下代码:

using System;
class Test
{
    public static void Main(string[] args)
    {
        foreach (var arg in args) Console.WriteLine(arg);
    }
}

然后我在项目设置中 编辑了命令行参数:项目属性

使用Ctrl+F5运行项目无法按预期工作。我在控制台中而不是在输出文件中打印命令行参数:

arg1
arg2
>
output.txt

如果我将命令行参数更改为:arg1 arg2 "> output.txt"我得到以下输出:

arg1
arg2
^> output.txt

我注意到output.txt在输出文件夹中创建了一个空文件。

这件事有可能完成还是我被迫继续使用 cmd.exe 来启动我的程序?

4

7 回答 7

11

严格来说,你不得不使用命令提示符来启动程序并重定向输出。否则,您需要自己解析命令行,GUI shell 可能不会这样做。

如果您只是想在 时重定向输出Start Debugging,则取消选中 的复选框Enable the Visual Studio hosting process,您就完成了。

如果您没有,并且"output.txt"您在那里看到的,实际上不是由您的应用程序生成的,而是"YourApplication.vshost.exe"在您开始调试之前由 Visual Studio IDE 生成的。内容永远是空的,不能写;因为它被Hosting Process锁定了。

fo1e8.png

但是,如果您希望应用程序的行为与您启动它的模式相同,那么事情会更加复杂。

当您开始使用应用程序进行调试时,它从以下内容开始:

“YourApplication.exe” arg1 arg2

因为输出已经被 IDE 重定向了。

当 you 时Start Without Debugging,它的开头是:

"%comspec%" /c ""YourApplication.exe" arg1 arg2 ^>output.txt & pause"

这是让您的应用程序获取您指定的所有参数的正确方法。

您可能想看看我之前的回答如何检测“按任意键继续......” 会显示?.

在这里,我在下面的代码中使用了类似返祖回归的方法:

  • 申请代码

    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);
            }
        }
    }
    

"devenv"因为在我们调试时父级将是 Visual Studio IDE(当前名为)。父进程和祖父进程实际上是多种多样的,我们需要一个规则来执行一些检查。

棘手的部分是孙子是真正遇到的人Main。每次运行时,代码都会检查祖父进程。如果祖父母是null它,那么它会产生,但产生的进程将是%comspec%,它也是新进程的父进程,它将以当前的相同可执行文件启动。因此,如果祖父母与自己相同,则它不会继续产卵,只会遇到Main.

代码中使用了静态构造函数,在Main. SO上有一个已回答的问题:静态构造函数如何工作?.

当我们开始调试时,我们正在调试祖父进程(产生的)。为了用孙子进程进行调试,我做Debugger.Launch了一个条件编译,它将调用Main,以保持Main清晰。

有关调试器的已回答问题也会有所帮助:将 C# 中的调试器附加到另一个进程

于 2013-04-29T22:38:17.200 回答
9

我不确定这是否可以在 Visual Studio 中完成。我的解决方案是为控制台设置一个新的输出。这个例子来自 MSDN:

Console.WriteLine("Hello World");
FileStream fs = new FileStream("Test.txt", FileMode.Create);
// First, save the standard output.
TextWriter tmp = Console.Out;
StreamWriter sw = new StreamWriter(fs);
Console.SetOut(sw);
Console.WriteLine("Hello file");
Console.SetOut(tmp);
Console.WriteLine("Hello World");
sw.Close();

http://msdn.microsoft.com/en-us/library/system.console.setout.aspx

于 2013-04-28T11:43:39.340 回答
4

在“开始选项”部分,以这种方式更改命令行参数文本框

args1 args2 1>output.txt

这会重定向标准输出 (1) 创建一个名为 output.txt 的文件
如果要附加到文件的先前版本,请写入

args1 args2 1>>output.txt

现在您可以在重定向输出控制台时逐步调试程序

于 2013-04-28T12:05:59.640 回答
3

我会建议一种更好的方法,而无需编写任何代码!

启动设置

只需配置 Visual Studio 即可将您的程序作为外部程序启动。

于 2013-05-07T15:41:18.677 回答
3

您可以.csproj.user在外部编辑器中打开并更改StartArguments来自:

<StartArguments>arg1 arg2 &gt; output.txt</StartArguments>

<StartArguments>arg1 arg2 > output.txt</StartArguments>
于 2013-04-28T11:53:10.030 回答
2

you are going to have to continue to use cmd.exe if you want to pipe your output to a file. the Command line arguments: is for command line arguments, therefore anything you try will be escaped to be a command line argument. Pipes and redirectors are not command line arguments, therefore they are getting escaped.

I would just create a .bat file that calls the program how I like. You could pin that to the taskbar and simply run it.

于 2013-05-06T21:36:46.183 回答
0

简单的方法是: 右键单击项目=>属性==>调试

在此处输入图像描述

然后确保在调试模式下运行您的程序。

于 2018-03-01T14:31:07.753 回答