C# 如何创建新的命令提示符窗口并重定向用户输入?

C# 如何创建新的命令提示符窗口并重定向用户输入?,c#,input,command-prompt,C#,Input,Command Prompt,对于游戏服务器应用程序,我已经有了一个控制台,其中显示了一些运行时信息。尽管如此,我仍然希望有另一个管理员可以输入命令(例如,在紧急情况下),而这些输入的结果仍然显示在主控制台窗口中 关于stackoverflow这个话题已经有类似的问题了,但是,应用这些答案并没有得到我所希望的结果。我很难理解为什么一方面,我似乎必须设置UseShellExecute=true实际获取一个新窗口,而这使得不可能RedirectStandardInput=true反之亦然。但是,通过一个新进程但在同一个控制台提示

对于游戏服务器应用程序,我已经有了一个控制台,其中显示了一些运行时信息。尽管如此,我仍然希望有另一个管理员可以输入命令(例如,在紧急情况下),而这些输入的结果仍然显示在主控制台窗口中

关于stackoverflow这个话题已经有类似的问题了,但是,应用这些答案并没有得到我所希望的结果。我很难理解为什么一方面,我似乎必须设置
UseShellExecute=true
实际获取一个新窗口,而这使得不可能
RedirectStandardInput=true反之亦然。但是,通过一个新进程但在同一个控制台提示符下进行输入和输出可以很好地工作,除了视觉混乱(在编写时,输出会附加到wirtten中,但不会发送输入,这非常不舒服)

那么,是否仍然可以对管理员输入使用单独的命令提示符(我想是这样),或者我必须设置另一种形式的进程间通信并创建单独的程序(带有Main函数和all)

下面是我当前关于进程生成的代码。请注意,如果您想了解整体组合,它将嵌入到一个不太精简的上下文中:

bool canExecute = false;

Process consoleProcess;
ProcessStartInfo startInfo = new ProcessStartInfo();

OperatingSystem os = Environment.OSVersion;
switch (os.Platform)
{
    case PlatformID.MacOSX:
        canExecute = false;
        break;
    case PlatformID.Unix:
        canExecute = true;
        startInfo.FileName = "/bin/bash";
        break;
    case PlatformID.Win32NT:
        canExecute = true;
        startInfo.FileName = "cmd.exe";
        break;
    case PlatformID.Win32S:
        canExecute = true;
        startInfo.FileName = "cmd.exe";
        break;
    case PlatformID.Win32Windows:
        canExecute = true;
        startInfo.FileName = "cmd.exe";
        break;
    case PlatformID.WinCE:
        canExecute = true;
        startInfo.FileName = "cmd.exe";
        break;
    case PlatformID.Xbox:
        canExecute = false;
        break;
}

startInfo.RedirectStandardInput = true;
startInfo.UseShellExecute = false;

consoleProcess = new Process();
consoleProcess.StartInfo = startInfo;
consoleProcess.Start();

if (canExecute)
{
    using (StreamWriter sw = consoleProcess.StandardInput)
    {
        String line;

        while ((line = Console.ReadLine()) != null)
        {
            // do something useful with the user input
        }
    }
}

提前谢谢你

仅使用内置的.NET
进程
类是无法做到这一点的。它不支持正确的选项

问题是,默认情况下,新的Windows控制台进程总是继承其父进程已分配的控制台。当您使用
UseShellExecute=true时
(进程的默认值),这会导致进程(当然)使用ShellExecuteEx()方法。由于新进程是通过Windows Shell而不是您的进程创建的,因此没有可继承的控制台,因此该进程拥有自己的控制台。但是如果直接创建进程,则会得到默认的控制台继承行为

但是,当然,因为您想要重定向标准I/O,所以不能使用
UseShellExecute=true。您必须将其设置为
false

唯一的解决方法是通过p/invoke直接调用
CreateProcess()
自己,这样就可以传递所需的标志,而
Process
类没有提供控制该标志的方法。有问题的标志是
CREATE\u NEW\u CONSOLE
。传递给函数调用,它告诉
CreateProcess()
函数为新进程创建一个单独的控制台

有关此行为以及如何调整以满足您的需要的更多信息,请参阅MSDN

很自然,这会打开一个全新的蠕虫程序罐,因为您将不再能够从
进程
类直接方便地帮助重定向I/O。从长远来看,您可能会发现只编写一个精简的非控制台代理程序来运行实际的程序更容易。这样,您就可以启动非控制台代理,它当然不会继承您当前的控制台,然后让它启动您真正想要运行的程序。这样做并不十分优雅(至少是因为代理不是一个控制台程序,您无法通过stdio轻松重定向I/O),但它相当简单,并使您处于托管代码世界中,API更易于使用


请在下面找到一个双向代理的示例(编译为“Windows应用程序”,而不是“控制台应用程序”),其中包含一个父进程和一个子进程。子进程只是将键入的内容回显到控制台。父进程向代理写入键入的任何内容。代理将从父级接收的任何内容发送给子级,并将从子级接收的任何内容发送给父级。所有进程都将空行输入视为终止条件

出于您自己的目的,您可能会使用单向管道(即代理使用
PipeDirection.In
,父进程使用
PipeDirection.Out
),并且代理重定向仅限于
StandardInput
。这样,所有输出仍将显示在子进程的窗口中。(双向示例更多地是为了证明概念……显然,如果输入和输出都是定向的,那么将子进程强制到它自己的窗口中就没有多大意义了:))

代理:(ConsoleProxy.exe)

子进程:(ConsoleApplication1.exe)

父进程:

class Program
{
    static void Main(string[] args)
    {
        NamedPipeServerStream pipe = new NamedPipeServerStream("ConsoleProxyPipe",
            PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);

        Console.Title = "Main Process";

        Process process = new Process();

        process.StartInfo.FileName = "ConsoleProxy.exe";
        process.StartInfo.Arguments = "ConsoleApplication1.exe ConsoleProxyPipe";
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.CreateNoWindow = true;

        process.Start();

        pipe.WaitForConnection();

        using (TextReader reader = new StreamReader(pipe))
        using (TextWriter writer = new StreamWriter(pipe))
        {
            Task readerTask = ConsumeReader(reader);
            string line;

            do
            {
                line = Console.ReadLine();
                writer.WriteLine(line);
                writer.Flush();
            } while (line != "");

            readerTask.Wait();
        }
    }

    static async Task ConsumeReader(TextReader reader)
    {
        char[] rgch = new char[1024];
        int cch;

        while ((cch = await reader.ReadAsync(rgch, 0, rgch.Length)) > 0)
        {
            Console.Write(rgch, 0, cch);
        }
    }
}

您可以保存一些代码重构,将其切换到字典中,以将操作系统类型映射到自定义类型,其中包含布尔值
可执行文件
和字符串
文件名
,当可执行文件为false时,该字符串为null。Ben Knoble,我知道字典的用法,并在其他地方使用它。在这里没关系,因为它大约一天只会被调用一次。canExecute变量也用于多个位置。截取的代码只是整个课程布局的拼凑。谢谢你的答案。因此,进程间通信(通过管道)终究是没有出路的。好吧,标准I/O也是一种管道但是,没有……不幸的是,
进程
类没有对进程的创建提供足够精确的控制,从而避免了某些工作。坦率地说,这是一个比较麻烦的问题:创建一个使用代理(如上所述)的托管代码专用解决方案,或者简单地执行p/invoke路由并直接使用所需的选项调用
CreateProcess()
。但对我来说,代理的尴尬被以下事实所抵消:通过非托管API处理重定向的标准I/O是一个非常重要的细节。
class Program
{
    static void Main(string[] args)
    {
        Console.Title = "ConsoleApplication1";

        string line;

        while ((line = PromptLine("Enter text: ")) != "")
        {
            Console.WriteLine("    Text entered: \"" + line + "\"");
        }
    }

    static string PromptLine(string prompt)
    {
        Console.Write(prompt);
        return Console.ReadLine();
    }
}
class Program
{
    static void Main(string[] args)
    {
        NamedPipeServerStream pipe = new NamedPipeServerStream("ConsoleProxyPipe",
            PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);

        Console.Title = "Main Process";

        Process process = new Process();

        process.StartInfo.FileName = "ConsoleProxy.exe";
        process.StartInfo.Arguments = "ConsoleApplication1.exe ConsoleProxyPipe";
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.CreateNoWindow = true;

        process.Start();

        pipe.WaitForConnection();

        using (TextReader reader = new StreamReader(pipe))
        using (TextWriter writer = new StreamWriter(pipe))
        {
            Task readerTask = ConsumeReader(reader);
            string line;

            do
            {
                line = Console.ReadLine();
                writer.WriteLine(line);
                writer.Flush();
            } while (line != "");

            readerTask.Wait();
        }
    }

    static async Task ConsumeReader(TextReader reader)
    {
        char[] rgch = new char[1024];
        int cch;

        while ((cch = await reader.ReadAsync(rgch, 0, rgch.Length)) > 0)
        {
            Console.Write(rgch, 0, cch);
        }
    }
}