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

How to create a new command prompt window and redirecting user input?

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

Whosebug 上已经有关于此主题的类似问题,但是,应用答案并没有得到我所希望的结果。我很难理解为什么一方面我似乎必须设置 UseShellExecute = true; 才能实际获得一个新的 window 而这使得 RedirectStandardInput = true; 变得不可能,反之亦然。但是,通过新进程但在同一个控制台提示中输入和输出工作正常,除了视觉混乱(当你写的时候,输出附加到你的 wirtten 而不是发送输入,这是非常不舒服的)。

所以,是否仍然可以使用单独的命令提示符进行管理员输入(我猜是这样),或者我是否必须设置另一种形式的进程间通信并创建一个单独的程序(使用 Main-function和所有)?

这是我当前关于进程生成的代码。请注意,如果您对整体构成感到疑惑,它嵌入在一个不太精简的上下文中:

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 Process class 无法做到这一点。它不支持正确的选项。

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

当然,既然你想重定向标准 I/O,你就不能使用 UseShellExecute = true;。您必须将其设置为 false.

解决这个问题的唯一方法是通过 p/invoke 自己直接调用 CreateProcess(),这样你就可以传递你需要的标志,Process class不提供控制方法。有问题的标志是 CREATE_NEW_CONSOLE。传递给函数调用,它告诉 CreateProcess() 函数为新进程创建一个单独的控制台。

请参阅 MSDN 的 Creation of a Console 了解有关此行为以及如何调整它以满足您的需要的更多信息。

自然地,这打开了一个全新的蠕虫罐头,因为您将不再能够从 Process class 直接方便地帮助重定向 I/O。您可能会发现在较长的 运行 中更容易,只需编写一个瘦的非控制台代理程序到 运行 实际程序。这样,您就可以启动非控制台代理,它当然不会继承您当前的控制台,然后让 启动您真正想要 运行 的程序。这样做并不是非常优雅(其中最重要的是,代理不是控制台程序,你将无法通过 stdio 轻松重定向 I/O),但它相当简单并且让你在托管代码世界,具有更易于使用的 API。


请在下面找到一个双向代理的示例(编译为 "Windows Application", 而不是 "Console Application"),具有父进程和子进程。子进程简单地将输入的任何内容回显到控制台。父进程将输入的任何内容写入代理。代理将从父进程接收到的任何内容发送给子进程,并将从子进程接收到的任何内容发送给父进程。所有进程都将空行输入视为终止条件。

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

代理服务器: (ConsoleProxy.exe)

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

        pipe.Connect();

        Process process = new Process();

        process.StartInfo.FileName = args[0];
        process.StartInfo.UseShellExecute = false;
        process.StartInfo.RedirectStandardInput = true;
        process.StartInfo.RedirectStandardOutput = true;

        process.Start();

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

            do
            {
                line = reader.ReadLine();
                if (line != "")
                {
                    line = "proxied write: " + line;
                }
                process.StandardInput.WriteLine(line);
                process.StandardInput.Flush();
            } while (line != "");

            readerTask.Wait();
        }
    }

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

        while ((cch = await reader.ReadAsync(rgch, 0, rgch.Length)) > 0)
        {
            writer.Write("proxied read: ");
            writer.Write(rgch, 0, cch);
            writer.Flush();
        }
    }
}

子进程: (ConsoleApplication1.exe)

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